My parent component is like this:
export default class MobileCompo extends React.Component {
constructor(props) {
super(props);
this.state = {
data: null,
datasets: {}
};
this.get_data = this.get_data.bind(this);
}
componentWillMount() {
this.get_data();
}
async get_data() {
const ret = post_api_and_return_data();
const content={};
ret.result.gsm.forEach((val, index) => {
content[val.city].push()
});
this.setState({data: ret.result.gsm, datasets: content});
}
render() {
console.log(this.state)
// I can see the value of `datasets` object
return (
<div>
<TableElement dict={d} content={this.state.data} />
<BubbleGraph maindata={this.state.datasets} labels="something"/>
</div>
)
}
}
child component:
export default class BubbleGraph extends React.Component {
constructor(props) {
super(props);
this.state = {
finalData: {datasets: []}
};
console.log(this.props);
// here I can't get this.props.maindata,it's always null,but I can get labels.It's confusing me!
}
componentWillMount() {
sortDict(this.props.maindata).forEach((val, index) => {
let tmpModel = {
label: '',
data: null
};
this.state.finalData.datasets.push(tmpModel)
});
}
render() {
return (
<div>
<h2>{this.props.labels}</h2>
<Bubble data={this.state.finalData}/>
</div>
);
}
}
I tried many times,but still don't work,I thought the reason is about await/async,but TableElement works well,also BubbleGraph can get labels.
I also tried to give a constant to datasets but the child component still can't get it.And I used this:
this.setState({ datasets: a});
BubbleGraph works.So I can't set two states at async method?
It is weird,am I missing something?
Any help would be great appreciate!
Add componentWillReceiveProps inside child componenet, and check do you get data.
componentWillReceiveProps(newProps)
{
console.log(newProps.maindata)
}
If yes, the reason is constructor methos is called only one time. On next setState on parent component,componentWillReceiveProps () method of child component receives new props. This method is not called on initial render.
Few Changes in Child component:
*As per DOC, Never mutate state variable directly by this.state.a='' or this.state.a.push(), always use setState to update the state values.
*use componentwillrecieveprops it will get called on whenever any change happen to props values, so you can avoid the asyn also, whenever you do the changes in state of parent component all the child component will get the updates values.
Use this child component:
export default class BubbleGraph extends React.Component {
constructor(props) {
super(props);
this.state = {
finalData: {datasets: []}
};
}
componentWillReceiveProps(newData) {
let data = sortDict(newData.maindata).map((val, index) => {
return {
label: '',
data: null
};
});
let finalData = JSON.parse(JSON.stringify(this.state.finalData));
finalData.datasets = finalData.datasets.concat(data);
this.setState({finalData});
}
render() {
return (
<div>
<h2>{this.props.labels}</h2>
<Bubble data={this.state.finalData}/>
</div>
);
}
}
Related
My React structure is
- App
|--SelectStudy
|--ParticipantsTable
In SelectStudy there is a button whose click triggers a message to its sibling, ParticipantsTable, via the App parent. The first Child->Parent transfer works. But how do I implement the second Parent->Child transfer? See questions in comments.
App
class App extends Component {
myCallback(dataFromChild) {
// This callback receives changes from SelectStudy Child Component's button click
// THIS WORKS
alert('SelectStudy Component sent value to Parent (App): ' + dataFromChild.label + " -> " + dataFromChild.value);
// QUESTION: How to Update State of ParticipantsTable (SelectStudy's Sibling) next?
// ........................................................
}
render() {
return (
<div className="App">
<SelectStudy callbackFromParent={this.myCallback}></SelectStudy>
<ParticipantsTable></ParticipantsTable>
</div>
);
}
SelectStudy
class SelectStudy extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
items: [],
selectedStudy: null,
isButtonLoading: false
};
this.handleButtonClick = this.handleButtonClick.bind(this);
}
render() {
const { error, isLoaded, items, itemsForReactSelect, selectedStudy, isButtonLoading } = this.state;
return <Button onClick={this.handleButtonClick}>Search</Button>;
}
handleButtonClick = () => {
this.props.callbackFromParent(this.state.selectedStudy);
}
}
ParticipantsTable - this needs to receive a certain variable, e.g. study in its State
class ParticipantsTable extends React.Component {
constructor(props) {
//alert('Constructor');
super(props);
// Initial Definition of this component's state
this.state = {
study: null,
items: [],
error: null
};
}
// THIS METHOD IS AVAILABLE, BUT HOW TO CALL IT FROM App's myCallback(dataFromChild)?
setStudy = (selectedStudy) => {
this.setState({study: selectedStudy});
}
render() {
return ( <div>{this.state.study}</div> );
}
}
The state should live definitively at the App level, not in the child. State needs to live one level above the lowest common denominator that needs access to it. So if both SelectStudy and ParticipantsTable need access to the same bit of state data, then it must live in their closest common ancestor (or above).
This is a core concept of React, known as "lifting state up", so much so that it has its own page in the official React documentation.
In your case, it would look something like this. Notice how state lives in only one place, at the <App /> level, and is passed to children via props.
import React from 'react';
class App extends React.Component {
// State lives here at the closest common ancestor of children that need it
state = {
error: null,
isLoaded: false,
items: [],
selectedStudy: null,
isButtonLoading: false
};
myCallback = (dataFromChild) => {
this.setState(dataFromChild);
};
render() {
return (
<div className="App">
{/* State is passed into child components here, as props */}
<SelectStudy data={this.state} callbackFromParent={this.myCallback}></SelectStudy>
<ParticipantsTable study={this.state.selectedStudy} />
</div>
);
}
}
class SelectStudy extends React.Component {
handleButtonClick = () => {
// Here we execute a callback, provided by <App />, to update state one level up
this.props.callbackFromParent({ ...this.props.selectedStudy, isButtonLoading: true });
};
render() {
const { error, isLoaded, items, itemsForReactSelect, selectedStudy, isButtonLoading } = this.props.data;
return <Button onClick={this.handleButtonClick}>Search</Button>;
}
}
// This component doesn't need to track any internal state - it only renders what is given via props
class ParticipantsTable extends React.Component {
render() {
return <div>{this.props.study}</div>;
}
}
I think what you need to understand is the difference between state and props.
state is internal to a component while props are passed down from parents to children
Here is a in-depth answer
So you want to set a state in the parent that you can pass as props to children
1 set state in the parent
this.state = {
value: null
}
myCallback(dataFromChild) {
this.setState({value: dataFromChild.value})
}
2 pass it as a prop to the children
class ParticipantsTable extends React.Component {
constructor(props) {
super(props);
this.state = {
study: props.study,
items: [],
error: null
};
}
Also, although not related to your question, if you learning React I suggest moving away from class-based components in favour of hooks and functional components as they have become more widely used and popular recently.
I am quite new with React and I have problem bellow
I have a parent component like this:
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {count:1};
}
shouldComponentUpdate(nextProps, nextState, nextContext) {
return false;
}
setCount = () => {
this.setState({
count: 2
});
};
render() {
const {name, running, onRun, onStop} = this.props;
return (
<div>
<Test count={this.state.count}/>
<p><a href="#" onClick={this.setCount}>SetCount</a></p>
</div>
);
}
}
And here is Test component
class Test extends React.Component {
constructor(props) {
super(props);
}
shouldComponentUpdate(nextProps, nextState, nextContext) {
return true;
}
render() {
const {count} = this.props;
return (
<div>
{console.log("Counting")}
<p>{count}</p>
</div>
);
}
}
I have method "shouldComponentUpdate" returns "false" in Parent component because I don't want to re-render it.
My understanding is React know which part of DOM need to be re-rendered. And in this case, the state of Parent changes will re-render "Test" component
But when I run above code, "Test" component does not redender.
Is there anything wrong in my code?
Thanks a lot for your help
You need to return true from your parent's shouldComponentUpdate method.
If you return false, after the initial render it won't update, even if you call a function that calls setState.
Is the refresh of the whole page are you talking about? If thats the case, probably you wanna change your <a> tag to button or use e.preventDefault();.
If not, I am not sure if that is possible. If you setState in the parent, it will rerender parent as well as the children. If you dont want to render the parent then you have to manage individual state management in the child level.
For example,
class Parent extends React.Component {
constructor(props) {
super(props);
}
render() {
const {name, running, onRun, onStop} = this.props;
return (
<div>
<Test/>
</div>
);
}
}
class Test extends React.Component {
constructor(props) {
super(props);
this.state = {count:1};
}
setCount = (e) => {
e.preventDefault();
this.setState({
count: 2
});
};
render() {
const {count} = this.state;
return (
<div>
{console.log("Counting")}
<p>{count}</p>
<p><a href="#" onClick={this.setCount}>SetCount</a></p>
</div>
);
}
}
I'm trying to call a function from application startup. The function reads data from JSON via dataVar (set elsewhere) and tries to load it into {items} for further consumption:
const dataVar = JSONStuff;
class Global extends Component {
constructor(props) {
super(props);
this.state = {
query: '',
items: []
}
this.init();
}
// componentDidMount() {
// This doesn't work either!
// this.init();
// }
init() {
let { items } = dataVar;
this.setState({items});
}
render() {
return (
<div className="Global">
<Gallery items={this.state.items}/>
</div>
)
}
}
Then in Gallery.js:
import React, { Component } from 'react';
class Gallery extends Component {
render() {
return (
<div>
<h3>gallery:</h3>
{
this.props.items.map((item, index) => {
let {title} = item.name;
return (
<div key={index}>{title}</div>
)
})
}
</div>
)
}
}
export default Gallery;
Not sure why Global can't call a function inside of itself. I've tried with and without "this." I either get error to where the app won't complile or I get:
"Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op."
First of all, it's a warning, you probably better not call setState in componentDidMount.
My suggestion 1: assign value to state in constructor
constructor(props) {
super(props);
this.state = {
query: '',
items: dataVar.items,
};
}
Suggestion 2:
Do inside the componentWillReceiveProps
componentWillReceiveProps(nextProps) {
const { dataVar: items } = nextProps; // pass dataVar as props
this.setState({
items,
});
}
Plus try to debug your props and pay attention on your console for errors.
I want to fetch data from server periodically and refresh rows when data is fetched by setState() but the rows doesn't re-render after setState().
constructor(props) {
super(props);
this.state = {
rows: []
}
this.refreshList = this.refreshList.bind(this);
}
refreshList() {
req.get('/data').end(function (error, res) {
// type of res is array of objects
this.setState({
rows: res
});
});
}
// call this method on button clicked
handleClick() {
this.refreshList();
}
render() {
return(
<div>
<button onClick={this.handleClick}>Refresh List</button>
<Table rows={this.state.rows}/>
</div>
);
}
when call refreshList() new feteched data doesn't render.
My table component is:
// Table component
export class Table extends Component {
constructor(props) {
super(props);
this.state = {
rows: props.rows
}
}
render() {
return (
<div>
{this.state.rows.map((row, i) => (
<div>{row.title}</div>
))}
</div>
)
}
}
Thanks a lot for your help. How can I refresh list on click button?
Your table component never changes its state after the construction. You can fix it easily by updating the state from new props:
export class Table extends Component {
constructor(props) {
super(props);
this.state = {
rows: props.rows
}
}
componentWillReceiveProps(newProps) {
this.setState({
rows: newProps.rows
});
}
render() {
return (
<div>
{this.state.rows.map((row, i) => (
<div>{row.title}</div>
))}
</div>
)
}
}
However, if your table component is so simple, you can make it stateless and use props directly without setState():
export class Table extends Component {
render() {
return (
<div>
{this.props.rows.map((row, i) => (
<div>{row.title}</div>
))}
</div>
)
}
}
Note there is no need for constructor now. We could actually make it a functional component.
Use an arrow function:
req.get('/data').end((error, res)=> {
// type of res is array of objects
this.setState({
rows: res
});
});
With the ES5 style callback function, the context of this gets lost.
You could also bind this directly to a local variable, i.e., var that = this and stick with the function syntax, but I think most would agree what the ES6 arrow syntax is nicer.
I'm new to React. I'm stuck on this, would really appreciate some help!
A parent component will pass an array into this child component. When I console.log(this.props.repairs) it shows me an array of 4. I am trying to update this.state.sortedDataList whenever the array of repairs is passed in. The console.log(this.state) is still showing sortedDataList as an empty array.
What am I doing wrong? Thanks so much, appreciate any help.
class Repairs extends React.Component {
constructor(props) {
super(props);
this.state = {
sortedDataList: []
};
}
componentWillReceiveProps(nextProps) {
if(this.props != nextProps) {
this.setState({
sortedDataList: this.props.repairs
});
}
}
render() {
console.log(this.props);
console.log(this.state);
return (
<div></div>
);
}
}
Never mind, found my silly mistake! If anyone else gets stuck on this in the future...
componentWillReceiveProps(nextProps) {
if(this.props != nextProps) {
this.setState({
sortedDataList: nextProps.repairs
});
}
}
componentWillReceiveProps isn't called on the first render. That is the reason that you don't see any update in the state
From the React Docs:
"Invoked when a component is receiving new props. This method is not called for the initial render."
If you want to make the change only first time you can make use of componentWillMount lifecycle function and update the state. On subsequent changed you componentWillReceiveProps will be called.
class Repairs extends React.Component {
constructor(props) {
super(props);
this.state = {
sortedDataList: []
};
}
componentWillMount() {
this.setState({
sortedDataList: this.props.repairs
}, () => console.log(this.state.sortedDataList));
}
componentWillReceiveProps(nextProps) {
if(this.props != nextProps) {
this.setState({
sortedDataList: nextProps.repairs
});
}
}
render() {
console.log("r",this.props);
console.log("r",this.state);
return (
<div></div>
);
}
}
class App extends React.Component {
render() {
var arr = ["1", "2", "3"];
return (
<div >
<Repairs repairs={arr}/>
</div>
)
}
}
ReactDOM.render(<App/>, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react-dom.min.js"></script>
<div id="app"></div>
In your constructor
this.state = {
sortedDataList: []
};
You initially set state to an empty array, so on first render it'll be empty. Then, whenever the props are changed, it'll get updated via componentWillReceiveProps().
As Shubham said, componentWillReceiveProps() isn't called on the first render. If you want the state to reflect the props right from the first render, you'd have to put it in the constructor like so:
this.state = {
sortedDataList: this.props.repair
};