Reputation: 1944
I am new to React/Redux and have a problem with state.
TrajectContainer.jsx
class TrajectContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
trajects: props.trajects,
onClick: props.onClick
};
}
componentWillReceiveProps(nextProps) {
console.log('componentWillReceiveProps', nextProps);
this.setState(nextProps);
}
render() {
// When the componentWillReceiveProps is not present, the this.state will hold the old state
console.log('rerender', this.state);
return (<div className="col-md-6">
<h2>Trajects</h2>
<button className="btn btn-primary" onClick={this.state.onClick}>Add new Traject</button>
{this.state.trajects.map(traject => <Traject traject={traject} key={traject.name}/>)}
</div>)
}
}
const mapStateToProps = function (store) {
console.log('mapToStateProps', store);
return {
trajects: store.trajects
};
};
const mapDispatchToProps = function (dispatch, ownProps) {
return {
onClick: function () {
dispatch(addTraject());
}
}
};
export default connect(mapStateToProps, mapDispatchToProps)(TrajectContainer);
When a reducer returns a new state, the component will rerender with the new data.
However: if I remove the componentWillReceiveProps function, the render() function has the old state.
I checked the data received in mapStateToProps, and this is new New State. So I don't understand why I need the componentWillReceiveProps function in order for the render function to receive the new data.
Am I doing something wrong?
Upvotes: 50
Views: 165150
Reputation: 4506
componentDidUpdate()
should now be used rather than componentWillReceiveProps
also see an article from gaearon re writing resilient components
There are two potential issues here
Avoiding state means you no longer need your constructor or life-cycle methods. So your component can be written as a stateless functional component there are performance benefits to writing your component in this way.
Below is a code snippet that removes the state from your component and relies on the state that has been returned from the redux store
import React from "react";
import { connect } from "react-redux";
const TrajectContainer = ({ trajects, addTraject }) => (
<div className="col-md-6">
<h2>Trajects</h2>
<button className="btn btn-primary" onClick={addTraject}>Add new Traject</button>
{trajects.map(traject => <Traject traject={traject} key={traject.name} />)}
</div>
);
const mapStateToProps = ({ trajects }) => ({ trajects });
export default connect( mapStateToProps, { addTraject })(TrajectContainer);
Upvotes: 20
Reputation: 104379
componentWillReceiveProps
is required if you want to update the state values with new props values, this method will get called whenever any change happens to props values.
In your case why you need this componentWillReceiveProps method?
Because you are storing the props values in state variable, and using it like this:
this.state.KeyName
That's why you need componentWillReceiveProps
lifecycle method to update the state value with new props value, only props values of component will get updated but automatically state will not get updated. If you do not update the state then this.state
will always have the initial data.
componentWillReceiveProps
will be not required if you do not store the props values in state and directly use:
this.props.keyName
Now react will always use updated props values inside render method, and if any change happen to props, it will re-render the component with new props.
As per DOC:
componentWillReceiveProps() is invoked before a mounted component receives new props. If you need to update the state in response to prop changes (for example, to reset it), you may compare this.props and nextProps and perform state transitions using this.setState() in this method.
React doesn't call componentWillReceiveProps with initial props during mounting. It only calls this method if some of component's props may update.
Suggestion:
Do not store the props values in state, directly use this.props
and create the ui components.
Upvotes: 81
Reputation: 66
The problem with your implementation is that you are duplicating your Redux store state (comming from the props) into your React state (this.state)
In your example, if store.trajects
is updated, then this.props.traject
will be updated and a render will be triggered only if this.props.traject
is used in your render method (not the case).
Since you are using the state instead of the prop in your render method, you have to manually change the state of you component using this.setState
to trigger a render.
This is not a good pattern: I would advise not to use the state, and use directly the props like this:
class TrajectContainer extends React.Component {
render() {
return (<div className="col-md-6">
<h2>Trajects</h2>
<button className="btn btn-primary" onClick={this.props.onClick}>Add new Traject</button>
{this.props.trajects.map(traject => <Traject traject={traject} key={traject.name}/>)}
</div>)
}
}
const mapStateToProps = function (store) {
console.log('mapToStateProps', store);
return {
trajects: store.trajects
};
};
const mapDispatchToProps = function (dispatch, ownProps) {
return {
onClick: function () {
dispatch(addTraject());
}
}
};
export default connect(mapStateToProps, mapDispatchToProps)(TrajectContainer)
Upvotes: 2
Reputation: 9408
In your case you will require componentWillReceiveProps
and you have to update the state when you receive new props. Because
In your constructor, you have declared your state as below. As you can see you construct your state
using the props that are passed in. (This is why you require componentWillReceiveProps
and the logic to update it there)
this.state = {
trajects: props.trajects,
onClick: props.onClick
};
So when your props, changes componentWillReceiveProps
is the function that gets called. The constructor does not gets called. So you have to set the state again so that the changes goes into the state of the component.
However your logic should be as below. With a condition so that you can prevent repeated state updates if its called multiple times.
componentWillReceiveProps(nextProps) {
console.log('componentWillReceiveProps', nextProps);
if (this.props !== nextProps) {
this.setState(nextProps);
}
}
One should store the props into state, only if you are going to modify the content of it. But in your case i see that there is no modification. So you can directly use this.props.trajects
directly instead of storing it into the state and then using it. This way you can get rid of the componentWillReceiveProps
So your render function will use something like below
{this.props.trajects.map(traject => //what ever is your code.... }
Upvotes: 10
Reputation: 821
I had similar issue add withRouter()
like this:
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(TrajectContainer));
Upvotes: 4