how to use getDerivedStatefromProps in my component - reactjs

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.

Related

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.

Set initial state from props in constructor doesn't work

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;
}

`componentWillReceiveProps` explanation

I recently wanted to upgrade my knowledge of React, so I started from the component lifecycle methods. The first thing that got me curious, is this componentWillReceiveProps. So, the docs are saying that it's fired when component is receiving new (not necessarily updated) props. Inside that method we can compare them and save into the state if needed.
My question is: Why do we need that method, if changes in props of that component (inside parent render) will trigger the re-render of this child component?
One common use case are state (this.state) updates that may be necessary in response to the updated props.
Since you should not try to update the component's state via this.setState() in the render function, this needs to happen in componentWillReceiveProps.
Additionally, if some prop is used as a parameter to some fetch function you should watch this prop in componentWillReceiveProps to re-fetch data using the new parameter.
Usually componentDidMount is used as a place where you trigger a method to fetch some data. But if your container, for example, UserData is not unmounted and you change userId prop, the container needs to fetch data of a user for corresponding userId.
class UserData extends Component {
componentDidMount() {
this.props.getUser(this.props.userId);
}
componentWillReceiveProps(nextProps) {
if (this.props.userId !== nextProps.userid) {
this.props.getUser(nextProps.userId);
}
}
render() {
if (this.props.loading) {
return <div>Loading...</div>
}
return <div>{this.user.firstName}</div>
}
}
It is not a full working example. Let's imagine that getUser dispatch Redux action and Redux assign to the component user, loading and getUser props.
It 'serves' as an opportunity to react to the incoming props to set the state of your application before render. If your call setState after render you will re-render infinitely and that's why you're not allowed to do that, so you can use componentWillReceiveProps instead.
But... you are beyond CORRECT in your confusion, so correct in fact that they are deprecating it and other Will-lifecycle hooks Discussion Deprecation.
There are other ways to accomplish what you want to do without most of those Will-lifecycle methods, one way being don't call setState after render, just use the new incoming props directly in render (or wherever) to create the stateful value you need and then just use the new value directly, you can then later set state to keep a reference for the next iteration ex: this.state.someState = someValue, this will accomplish everything and not re-render the component in an infinite loop.
Use this as an opportunity to react to a prop transition before render() is called by updating the state using this.setState(). The old props can be accessed via this.props. Calling this.setState() within this function will not trigger an additional render.
Look at this article
the componentWillReceiveProps will always receive as param "NxtProps", componentWillReceiveProps is called after render().
some people use this method use this to compare nxtProps and this.props to check, if something should happen before the component call render, and to some validations.
check the react's documentation to know more about react lifecycle!
hope this could help you!
changes in props of that component (inside parent render) will trigger the re-render of this child component
You are absolutely right. You only need to use this method if you need to react to those changes. For instance, you might have a piece of state in a child component that is calculated using multiple props.
Small Example:
class Test extends Component {
state = {
modified: "blank"
};
componentDidMount(){
this.setState({
modified: `${this.props.myProp} isModified`
});
}
componentWillReceiveProps(nextProps) {
this.setState({
modified: `${nextProps.myProp} isModified`
});
}
render() {
return <div className="displayed">{this.state.modified}</div>
}
}
In this example, componentDidMount sets the state using this.props. When this component receives new props, without componentWillReceiveProps, this.state.modified would never be updated again.
Of course, you could just do {this.props.myProp + "IsModified"} in the render method, but componentWillReceiveProps is useful when you need to update this.state on prop changes.

Set state in React component from props and update when props change?

My React component sets a state property called userInGroup based on props.
this.state = {
userInGroup: this.props.user
? this.props.user.groups.includes(props.group._id)
: false,
};
This works but does not update when the props change and the value of userInGroup should also change, until I refresh the page. How can I make this update reactively?
Maybe I could use componentWillUpdate or componentDidUpdate but then Id be repeating the logic used by userInGroup. Is this repetition inevitable?
You need to use getDerivedStateFromProps. The other methods are now deprecated and deemed unsafe.
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.
This method exists for rare use cases where the state depends on changes in props over time. For example, it might be handy for implementing a component that compares its previous and next children to decide which of them to animate in and out.
static getDerivedStateFromProps(props) {
return {
userInGroup: props.user
? props.user.groups.includes(props.group._id)
: false,
}
}
Yes you need to make use of componentWillReceiveProps along with constructor/componentDidMount, when you want to update state when props change since constructor/componentDidMount are called only once when the component mounts, and componentWillReceiveProps() is invoked before a mounted component receives new props or the Parent component updates
You could write a function that contains the logic
this.state = {
userInGroup: this.getUserStatus();
};
componentWillReceiveProps(nextProps) {
if(nextProps.user !== this.props.user) {
this.setState({userInGroup: this.getUserStatus(nextProps)})
}
}
getUserStatus = (newProps) => {
const data = newProps || this.props
return data.user
? data.user.groups.includes(data.group._id)
: false
}

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