I'm building a template component using React with TypeScript but i'm facing an issue i'm unable to solve. I'm posting this in case anyone knows how to approach it.
My project has a MyComp component that invokes TemplateComp using a subcomponent GraphComp and the data that Graph requires.
TemplateComp invokes and stylises the Graph subcomponent plus adds some props that are needed (such as customPropertyA) next to graphData.
GraphComp is requiring certain parameters that graphData needs to render properly.
The issue i'm facing is related to the types definition from GraphComp to MyComp while passing through TemplateComp. It may seem that (because TemplateComp defines graphData as any, as it is unknown to it) MyComp understands that graphData can also be any, but in reality it should be equal to the properties that Graph is requiring as Props (but not all of them).
Is there any way to let MyComp and TemplateComp infer the types that GraphComp is asking for?
Here is my code:
import { Component, ElementType } from 'react'
export default class MyComp extends Component<{}, {}> {
render() {
return (
<div>
<TemplateComp
Graph={GraphComp}
graphData={{
value: 0
}}
/>
</div>
)
}
}
class TemplateComp extends Component<
{
Graph: ElementType
graphData: any
},
{}
> {
customPropertyA = 'hello'
render() {
const { graphData, Graph } = this.props
return (
<div>
<Graph {...graphData} customPropertyA={this.customPropertyA} />
</div>
)
}
}
class GraphComp extends Component<
{
value: number
customPropertyA: string
},
{}
> {
render() {
return <div>my value: {this.props.value}</div>
}
}
I'm perfectly fine with modifying how these components work. However, i still need the 3 layer approach and to be able to define GraphComp's props from within MyComp and TemplateComp separately.
For those wondering how to approach this, i managed to fix it by calling the mounted JSX instead of mounting it in TemplateComp and removed properties from it as they can also be described in MyComp.
import { Component, ElementType } from 'react'
export default class MyComp extends Component<{}, {}> {
render() {
return (
<div>
<TemplateComp
Graph={<GraphComp value={0} customPropertyA={"hello"} />}
/>
</div>
)
}
}
class TemplateComp extends Component<
{
Graph: JSX.Element
},
{}
> {
render() {
const { Graph } = this.props
return (
<div>
{Graph}
</div>
)
}
}
class GraphComp extends Component<
{
value: number
customPropertyA: string
},
{}
> {
render() {
return <div>my value: {this.props.value}</div>
}
}
Related
I'm trying to replicate same code here with different JSON, but the data is not loading.
Please help, I'm not sure what is missing in the code.
import React from 'react';
export default class ItemLister extends React.Component {
constructor() {
super();
this.state = { items: [] };
}
componentDidMount() {
fetch('http://media.astropublications.com.my/api/drebar_landing.json')
.then(result=>result.json())
.then(items=>this.setState({items}));
}
render() {
return(
<ul>
{this.state.items.length ?
this.state.items.map(item=><li key={item.id}>{item.Title}</li>)
: <li>Loading...</li>
}
</ul>
)
}
}
Your api response contains an object ArticleObject and the ArticleObject has array of objects so you need to set the items.ArticleObject to the state.
Take a look at below solution for better understanding
componentDidMount() {
fetch('http://media.astropublications.com.my/api/drebar_landing.json')
.then(result=>result.json())
.then(items=>this.setState({items:items.ArticleObject}));
}
I was having trouble passing the to to the Link component of react-router-dom in the Tab element of the material-ui core.
I finally came up with this solution:
import * as React from 'react';
import Tabs from '#material-ui/core/Tabs';
import Tab from '#material-ui/core/Tab';
import { Link } from 'react-router-dom';
interface Props {
title?: string;
}
interface State {
value: number;
}
class NavButtons extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { value: 0 };
}
public handleOnChange = (event: any, value: number) => {
this.setState({ value })
}
public render() {
const {value} = this.state
return (
<Tabs value={value} onChange={this.handleOnChange} >
<Tab label="Home" component={Link} {...{to:"/"} as any} />
<Tab label="Contact" component={Link} {...{to:"/contact/"} as any} />
</Tabs>
)
}
}
export default NavButtons
The only problem is that I can't seem to find out what ...{} as any does in the documentation of material-ui or react.
Can someone explain this to me? I see a lot of React programmers use it but I have no idea what it does exactly.
typescript. what is mean: (this as any)
Looks like it's a TypeScript syntax. Adding as any could remove the type checking of {to:"/"} so it won't cause any warning/error.
handleShowMatchFacts = id => {
// console.log('match', id)
return fetch(`http://api.football-api.com/2.0/matches/${id}?Authorization=565ec012251f932ea4000001fa542ae9d994470e73fdb314a8a56d76`)
.then(res => {
// console.log('match facts', matchFacts)
this.props.navigator.push({
title: 'Match',
component: MatchPage,
passProps: {matchInfo: res}
})
// console.log(res)
})
}
I have this function above, that i want to send matchInfo to matchPage.
I take in that prop as follows below.
'use strict'
import React from 'react'
import { StyleSheet, View, Component, Text, TabBarIOS } from 'react-native'
import Welcome from './welcome.js'
import More from './more.js'
export default class MatchPage extends React.Component {
constructor(props) {
super(props);
}
componentWillMount(){
console.log('mathc facts ' + this.props.matchInfo._bodyInit)
}
render(){
return (
<View>
</View>
)
}
}
All the info I need is in that object - 'this.props.matchInfo._bodyInit'. My problem is that after '._bodyInt', I'm not sure what to put after that. I've tried .id, .venue, and .events, they all console logged as undefined...
You never change props directly in React. You must always change the state via setState and pass state to components as props. This allows React to manage state for you rather than calling things manually.
In the result of your api call, set the component state:
this.setState({
title: 'Match',
component: MatchPage,
matchInfo: res
}
Then pass the state as needed into child components.
render() {
return(
<FooComponent title={this.state.title} matchInfo={this.state.matchInfo} />
);
}
These can then be referenced in the child component as props:
class FooComponent extends Component {
constructor(props) {
super(props);
}
componentWillMount() {
console.log(this.props.title);
console.log(this.props.matchInfo);
// Etc.
}
}
If you need to reference these values inside the component itself, reference state rather than props.
this.state.title;
this.state.matchInfo;
Remember components manage their own state and pass that state as props to children as needed.
assuming you are receiving json object as response , you would need to parse the response before fetching the values.
var resp = JSON.parse(matchInfo);
body = resp['_bodyInit'];
How to invoke React component's function when this component is given in variable? I have a Parent that passes Test class into Child component, and this child wants to change something in Test.
export class Parent extends React.Component {
render() {
let test = (<Test />);
return (<Child tester={test} />);
}
}
export class Child extends React.Component {
render() {
this.props.tester.setText("qwerty"); // how to invoke setText, setState or something like that?
return ({this.props.tester});
}
}
export class Test extends React.Component {
constructor(props) {
super(props);
this.state = {
text: this.props.text || ""
};
}
setText(text) {
this.setState({ text: text });
}
render() {
return (<div>{this.state.text}</div>);
}
}
I think you should think about life cycle of react components.
Please try the code below(I just added logging), and observe logs carefully.
export class Parent extends React.Component {
render() {
let test = (<Test />);
return (<Child tester={test} />);
}
}
export class Child extends React.Component {
render() {
console.log("Child render"); // <= logging added!
// this.props.tester.setText("qwerty");
// What kind of object is 'this.props.tester(= <Test />)' here???
return ({this.props.tester});
}
}
export class Test extends React.Component {
constructor(props) {
super(props);
console.log("Test constructor"); // <= logging added!
this.state = {
text: this.props.text || ""
};
}
setText(text) {
// this.setState({ text: text });
// this is another problem. We cannot call setState before mounted.
this.state.text= text;
}
render() {
return (<div>{this.state.text}</div>);
}
}
If so, you will see 2 important facts.
'Test' component is not instantiated yet, when you call 'setText'.
How can we call a method of object which is not instantiated? Cannot!
this means 'this.props.tester' is not an instance of 'Test' component.
But if you really want to exec your code, modify Child.render like this.
render() {
var test = new Test({props:{}});
// or even this can work, but I don't know this is right thing
// var test = new this.props.tester.type({props:{}});
test.setText("qwerty");
return test.render();
}
But I don't think this is a good way.
From another point of view, one may come up with an idea like,
render() {
// Here, this.props.tester == <Test />
this.props.tester.props.text = "qwerty";
return (this.props.tester);
}
but of course it's not possible, because 'this.props.tester' is read-only property for Child.
I'm looking for the good syntax to pass a variable from a parent container to a child container.
Let's say I have theses routes, with a global widget list on / and specific widget lists on /widgets/:WidgetListID.
Note: I use react-router-relay
<Route
path='/' component={Layout}
>
<IndexRoute
component={WidgetListContainer}
queries={ViewerQueries}
/>
<Route
path='/widgets/:WidgetListID'
component={WidgetListContainer}
queries={ViewerQueries}
/>
</Route>
It's the same <WidgetList/> component, rendered inside <WidgetListContainer/> inside <Layout/> and here is how I try to pass the WidgetListID variable:
Layout.js
class Layout extends React.Component {
render() {
return (
<div>
...
{children}
...
</div>
);
}
}
WidgetListContainer.js
class WidgetListContainer extends React.Component {
render () {
return (
<div>
...
<WidgetList
viewer={viewer}
/>
</div>
)
}
}
export default Relay.createContainer(WidgetListContainer, {
initialVariables: {
WidgetListID: null
},
fragments: {
viewer: ($WidgetListID) => Relay.QL`
fragment on User {
${WidgetList.getFragment('viewer', $WidgetListID)}
}
`,
},
})
WidgetList.js
class WidgetList extends React.Component {
render () {
return (
<div>
<ul>
{viewer.widgets.edges.map(edge =>
<li key={edge.node.id}>{edge.node.widget.name}</li>
)}
</ul>
</div>
)
}
}
export default Relay.createContainer(WidgetList, {
initialVariables: {
WidgetListID: null
},
fragments: {
viewer: () => Relay.QL`
fragment on User {
widgets(first: 10, WidgetListID:$WidgetListID) {
edges {
node {
id,
name
}
}
}
}
`,
},
})
I have no problem setting the WidgetListID variable directly inside the WidgetList relay container, it works perfectly fine, but as soon as I try to pass it from the WidgetListContainer relay container I have an empty data object {__dataID__: "VXNlcjo="}. Though, the variable is well printed in my getWidget() function. So something doesn't work at some point but I can't figure out what?
What would be the good syntax to pass the WidgetListID variable from the parent container to the child container?
In the WidgetListContainer, change this:
fragments: {
viewer: ($WidgetListID) => Relay.QL`
fragment on User {
${WidgetList.getFragment('viewer', $WidgetListID)}
}
`,
},
to
fragments: {
viewer: ({WidgetListID}) => Relay.QL`
fragment on User {
${WidgetList.getFragment('viewer', {WidgetListID})}
}
`,
},
The first argument to the fragment builder is the Relay variables. So first you need to pull the WidgetListID variable out of the WidgetListContainer's variables, and then you can pass it into WidgetList.getFragment().
Note that the $ symbol is only used inside the Relay.QL template string. Inside the variables object, you refer to the variable by name, without the $.
Hi I also struggled with this for a bit.
Change this:
class WidgetListContainer extends React.Component {
render () {
return (
<div>
...
<WidgetList
viewer={viewer}
/>
</div>
)
}
}
to this:
class WidgetListContainer extends React.Component {
render () {
return (
<div>
...
<WidgetList
WidgetListID={this.props.relay.variables.WidgetListID}
viewer={viewer}
/>
</div>
)
}
}
also in your WidgetList component's relay container don't forget to set initial variables to
initialVariables: {
WidgetListID: null
},
this setup will make your WidgetListID variable available for your WidgetList component's relay container.