Set initial state from props in constructor doesn't work - reactjs

I have a react.js typescript app, where I have a component (OrganizationsSearch) that receives its props from a parent component (Organizations), which in turn receives its props from redux.
In the OrganizationsSearch component, i initialize state like this:
export default class OrganizationsSearch extends React.Component<Props, State> {
constructor(props: Props) {
super(props)
this.state = {
filteredOrganizations: this.props.organizations,
filterQuery: ""
}
}
...
The problem is when i navigate to the component through my route setup, the filteredOrganizations state is not equal to props.organizations. filteredOrganizations is just an empty array.
As can be seen in the code above, i have a filterQuery state property. This property is attached to an input field. Whenever i change the input, a filter function is run that updates the state based on this property. When the function is triggered, the state is correctly updated to contain the organizations that match the query. If i delete the input field content so that it's equal to the initial state value, the filteredOrganizations state contain all of the organizations as it should also do initially on component load.
What do i have to do to set the initial state to be equal to the props?

I think your problem could be that when the constructor of the OrganizationsSearch runs there is no data in this.props.organizations. You can easily check this with a console.log.
You could fix it that you only diplay the OrganizationsSearch component when the organization data is ready.You can display a Loading... text until then or some kind of progress indicator.
render() {
...
{organization ? <OrganizationsSearch organization={organization} /> : <div>Loading...</div>}
...
}
Another approach can be, that you do not store the filteredOrganization in the state. You can create it on the fly from organization and the filter value for the renderer. So there is only a single source of truth and you can avoid data inconsistencies. Storing prop data in state is discouraged anyway.

I think it might be about your component lifecycle.
The constructor is only called when the component is .. constructed. So depending on when redux sets your props, it might be too late, your component has already been "constructed" with an empty prop.
You can learn more in this kind of article https://blog.bitsrc.io/understanding-react-v16-4-new-component-lifecycle-methods-fa7b224efd7d
TL;DR you cant use the static function getDerivedStateFromProps for exemple :
static getDerivedStateFromProps(props, state) {
if (state.value !== props.value) {
return {
derivedValue: deriveValueFromProps(props),
mirroredProp: props.value
}
}
// when null is returned no update is made to the state
return null;
}

Related

If React props are readonly, how they can change (whats the usage of getDerivedStateFromProps)?

I'm learning React. I learned that props in react are readonly.
But i read somewhere:
getDerivedStateFromProps updates the state based on prop changes
So i got confused. If props are readonly, how they can change?
They are readonly in the context of a component, meaning, you can't assign a new value to them.
But a parent that passes props to child, can pass different props based on some condition. In that case, the child get rendered with a different props.
Example:
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
value: 0,
};
}
render() {
return (
<div>
<Child prop1={this.state.value} />
<button onClick={() => this.setState({ value: this.state.value + 1 })}>Click me</button>
</div>
);
}
}
In my example, each click on the parent's button will change it state, therefore, React will re-render it, and then this parent will pass a new value to Child component.
I think you need to understand component life-cycle and how to use and handle props which is described beautifully in below links:
https://reactjs.org/docs/react-component.html#static-getderivedstatefromprops
https://reactjs.org/docs/components-and-props.html#props-are-read-only
Props are read only means you can't change the value, but it doesn't mean you can't update it, for that you need to pass updated props back to child component and go any lifecycle methods to it.
An update can be caused by changes to props or state.
when there is update render() method is get called.
you are saying update to props, which directly means you are changing and modifying child,
props changed means, the data passed from parent is changed which leads in changing props,
always remember state passed from parent is considered as props for child is key point.
static getDerivedStateFromProps(props, state) gets called right after render() used rarely mailnly for animations transitions purposes.
the things possible with componentWillRecieveProps() and componentDidUpdate() is now done by getDerivedStateFromProps(), it will return object in response to change in props, and null if no change, sounds wonderful.
as you can see method is static you cant change any data, you can just play with your nextProps and prevState only.
this is useful for components having async api calls.

how to use getDerivedStatefromProps in my component

The component receives a list of Objects as prop. I want to display it with infinite scroll.
It has the state "curNum", which is current number of items to be displayed. "curNum" changes when scroll down so the list of items is sliced based on the "curNum".
The list of object will be updated by the parent component, and "curNum" should be reset to initial value.
With componentWillReceiveProps I can do:
componentWillReceiveProps(newProps) {
this.setState({
curNum: initNum,
});
}
but how can I rewrite it using getDerivedStateFromProps? I read that the new method will be triggerged even if the state changes. So how can I know I am recieving new props?
Do I have to mirror a copy of the list to the sate and then deep check if the list of objects are equal every time?
There are a few ways that you can use to update the state when props change
Use getDerivedStateFromProps: Note that its suggested that you avoid using this method as much as possible since this is called on each update and initial render and not just on Parent component re-render or props change. If however you want to use it, you need to store the prevState too
Code:
static getDerivedStateFromProps(props, state) {
if (state.prevCurNum !== props.initNum) {
return {
currNum: props.initNum,
prevCurNum: props.initNum
}
} else {
return { prevCurNum: props.initNum }
}
}
Assign the state from props in constructor and control the key props to component
code
<MyComponent key={this.state.initNum} initNum={this.state.initNum} />
and in MyComponent
constructor(props) {
super(props);
this.state= {
currNum: props.initNum
}
}
In the above example if the prop initNum changes, the key to the component will change and it will result in the component to re-mount calling its constructor
The third way is to use memoization in render, but its mostly useful when the state is derived from a complex computation of props and isn't supposed to change locally.

why mapStateToProps will rerender,that is meaning call the render() in Component

I think in react frame, the view changes with Component's state,
so I think when mapStateToProps run,and the component get a new store's state,
which mean the props of the component change.
according to react frame ,in this case, the view will not rerender unless the this.setState is called
I'll give one example.
1) You first load your component
2) You make changes on your component, thus changing your redux state for that component
Now, if you want to see the new props that are associated with this component you will see them in your 'render()' method. That's because the react component will re-render automatically if the props changed (as mentioned by Peter Ambruzs). The thing is, if you want to use these new props outside your render function, you will have to update your props(or your local state).
In this case you should use getDerivedStateFromProps (the new componentWillReceiveProps), for example:
constructor(props) {
super(props);
state = {
stateExample: this.props,
}
}
static getDerivedStateFromProps(nextProps, prevState) {
if(nextProps.stateExample !== prevState.stateExample) {
return { stateExample: nextProps.stateExample }
} else return null;
}
This will update your local state with the new props that you just changed on your redux store.

ReactJS Is it adviceable to update props

Is it adviceable to change the props to load my page with fresh data.
I have a requirement where I need to load the HTML page from JSON, example given below
formSchema:{
"label": "Step 1",
"action": "Next",
"Fields":[
{
"type":"text",
"label":"First Name",
"name":"fname"
"value":"Abraham"
}
]
}
I'm able to load the HTML page from the above JSON schema, but when user clicks the submit button there will a new schema loaded through ajax call. Now if I pass the new schema to my reducers can I directly replace the props with the newSchema, as I heard mutating the props is not adviceable. In such case how do I update my props only with new data.
If I do it as follows in my reducer
return Object.assign(...formSchema, action.formSchema);
I see in console the next state is shown as object instead of an array even though I load the same set of data.
You can use componentWillReceiveProps() method in React to update your view with new props.
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.
Note that React may call this method even if the props have not changed, so make sure to compare the current and next values if you only want to handle changes. This may occur when the parent component causes your component to re-render.
React doesn't call componentWillReceiveProps with initial props during mounting. It only calls this method if some of component's props may update. Calling this.setState generally doesn't trigger componentWillReceiveProps.
It looks like this:
class YourComponent extends Component{
constructor(props) {
this.state = {
message: props.message // initial prop
}
}
componentWillReceiveProps(nextProps){
this.setState({message: nextProps.message} // updated prop
}
render() {
return (
<div><h1>{this.state.message}</h1></div>
)
}
}
Note: In this code you're not updating/mutating props, instead you're using new props (nextProps) and updating the state of your view.

React Child Component Not Updating After Parent State Change

I'm attempting to make a nice ApiWrapper component to populate data in various child components. From everything I've read, this should work: https://jsfiddle.net/vinniejames/m1mesp6z/1/
class ApiWrapper extends React.Component {
constructor(props) {
super(props);
this.state = {
response: {
"title": 'nothing fetched yet'
}
};
}
componentDidMount() {
this._makeApiCall(this.props.endpoint);
}
_makeApiCall(endpoint) {
fetch(endpoint).then(function(response) {
this.setState({
response: response
});
}.bind(this))
}
render() {
return <Child data = {
this.state.response
}
/>;
}
}
class Child extends React.Component {
constructor(props) {
super(props);
this.state = {
data: props.data
};
}
render() {
console.log(this.state.data, 'new data');
return ( < span > {
this.state.data.title
} < /span>);
};
}
var element = < ApiWrapper endpoint = "https://jsonplaceholder.typicode.com/posts/1" / > ;
ReactDOM.render(
element,
document.getElementById('container')
);
But for some reason, it seems the child component is not updating when the parent state changes.
Am I missing something here?
There are two issues with your code.
Your child component's initial state is set from props.
this.state = {
data: props.data
};
Quoting from this SO Answer:
Passing the intial state to a component as a prop is an anti-pattern
because the getInitialState (in our case the constuctor) method is only called the first time the
component renders. Never more. Meaning that, if you re-render that
component passing a different value as a prop, the component
will not react accordingly, because the component will keep the state
from the first time it was rendered. It's very error prone.
So if you can't avoid such a situation the ideal solution is to use the method componentWillReceiveProps to listen for new props.
Adding the below code to your child component will solve your problem with Child component re-rendering.
componentWillReceiveProps(nextProps) {
this.setState({ data: nextProps.data });
}
The second issue is with the fetch.
_makeApiCall(endpoint) {
fetch(endpoint)
.then((response) => response.json()) // ----> you missed this part
.then((response) => this.setState({ response }));
}
And here is a working fiddle: https://jsfiddle.net/o8b04mLy/
If the above solution has still not solved your problem I'll suggest you see once how you're changing the state, if you're not returning a new object then sometimes react sees no difference in the new previous and the changed state, it's a good practice to always pass a new object when changing the state, seeing the new object react will definitely re-render all the components needing that have access to that changed state.
For example: -
Here I'll change one property of an array of objects in my state, look at how I spread all the data in a new object. Also, the code below might look a bit alien to you, it's a redux reducer function BUT don't worry it's just a method to change the state.
export const addItemToCart = (cartItems,cartItemToBeAdded) => {
return cartItems.map(item => {
if(item.id===existingItem.id){
++item.quantity;
}
// I can simply return item but instead I spread the item and return a new object
return {...item}
})
}
Just make sure you're changing the state with a new object, even if you make a minor change in the state just spread it in a new object and then return, this will trigger rendering in all the appropriate places.
Hope this helped. Let me know if I'm wrong somewhere :)
There are some things you need to change.
When fetch get the response, it is not a json.
I was looking for how can I get this json and I discovered this link.
By the other side, you need to think that constructor function is called only once.
So, you need to change the way that you retrieve the data in <Child> component.
Here, I left an example code: https://jsfiddle.net/emq1ztqj/
I hope that helps.
Accepted answer and componentWillReceiveProps
The componentWillReceiveProps call in accepted answer is deprecated and will be removed from React with version 17 React Docs: UNSAFE_componentWillReceiveProps()
Using derived state logic in React
As the React docs is pointing, using derived state (meaning: a component reflecting a change that is happened in its props) can make your components harder to think, and could be an anti-pattern. React Docs: You Probably Don't Need Derived State
Current solution: getDerivedStateFromProps
If you choose to use derived state, current solution is using getDerivedStateFromProps call as #DiogoSanto said.
getDerivedStateFromProps is invoked right before calling the render method, both on the initial mount and on subsequent updates. It should return an object to update the state, or null to update nothing. React Docs: static getDerivedStateFromProps()
How to use componentWillReceiveProps
This method can not access instance properties. All it does describing React how to compute new state from a given props. Whenever props are changed, React will call this method and will use the object returned by this method as the new state.
class Child extends React.Component {
constructor() {
super(props);
// nothing changed, assign the state for the
// first time to teach its initial shape.
// (it will work without this, but will throw
// a warning)
this.state = {
data: props.data
};
}
componentWillReceiveProps(props) {
// return the new state as object, do not call .setState()
return {
data: props.data
};
}
render() {
// nothing changed, will be called after
// componentWillReceiveProps returned the new state,
// each time props are updated.
return (
<span>{this.state.data.title}</span>
);
}
}
Caution
Re-rendering a component according to a change happened in parent component can be annoying for user because of losing the user input on that component.
Derived state logic can make components harder to understand, think on. Use wisely.

Resources