React.js/Redux - How to update state if props wasn't changed? - javascript

I want to update my local state if the props were changed. But I could not find a suitable solution to this problem. getDerivedStateFromProps gives me only nextProps and prevState but I need both prevProps and nextProps. Please write any your solution that might solve this problem)
Situation: The Cart component (stateful) gets the fetched data from the props and puts that data in this.state.cart. The Cart component has counterHandler method that triggers whenever the counter is changed (onIcrease, onDecrease, onChange). This handler calculates the data and makes a new data based on the event ( calculating totalPrice and updating the amount of the item ). Then puts the new data into setState to update the UI. The state doesn't change because I do this:
static getDerivedStateFromProps(nextProps, prevState) {
if (!_.isEqual(nextProps.cart, prevState.cart)) {
return { cart: nextProps.cart };
} else return null;
}
I do this because I want to update the state when the real data is changed ( the data that comes from the props ).
Goal: updating the counter's and the total price's value without making any-side effect ( the values might be updated in the store or only for the local state ).
This is what I mean:

You might want to use componentDidUpdate which is called when the props on the component update. It receives prevProps and prevState as parameters, and you have access to "nextProps" as this.props.
For example
componentDidUpdate(prevProps, prevState) {
if (!_.isEqual(this.props.cart, prevState.cart)) {
this.setState ({ cart: this.props.cart });
}
}

You can use componentWillReceiveProps method of react class. This method will be executed when parent component will render, its child will execute componentWillReceiveProps method in child. In this method in child you can update state based on new props.
componentWillReceiveProps(nextProps){
//You can access current props via this.props
//You can access newProps from argument
//Change component state after comparing both props
}

Related

react child props does not update when sending updated parent state

i have a chart component and i'm fetching data from an api through the props data to put in chart but when i'm sending the parent to chart component as props it's not updating the parent state is updated but the child props are not updating and if i log it in
componentWillReceiveProps(props) {
console.log(props);
// it shows updated value but if i do this
this.loadData();
}
loadData = () => {
console.log(props)
// it's value is old and not updated
}
i have check it in parent the state is updated is and i have a click event in parent when i first click it the parent state changes but chart component props will not change and if i click second time chart component props will the state of first time click state
here's my github repo https://github.com/naveenkash/forex
look in chart component props and pages index.js chart in getting props through index.js
because in componentWillReceiveProps props is reference to new props, they are not yet available in other parts of class, so you should pass them to loadData
componentWillReceiveProps(props){
this.loadData(props);
}
loadData=(props)=>{
console.log(props)
}
What Dmitry said is accurate, this.props hasn't changed as of componentWillReceiveProps(props). Therefore when you call this.loadData() you're still calling it with the old props.
Note that componentWillReceiveProps(props) is
unsafe
You should use componentDidUpdate(previousProps), where you can call this.loadData() and it will execute with updated this.props.
It's js scope issue not react event lift cycle issue, considering changing the code as below:
componentWillReceiveProps(props) {
console.log(props);
// it shows updated value but if i do this
this.loadData(props);
}
loadData = (newProps) => {
console.log(newProps)
// it's value is old and not updated
}

Redux, react. How I can add mapStateToProps into this.state?

I have action and middleware, where I make a fetch request.
I get a data in mapStateToProps.
But I want use this data in my state and change them in setState.
How I can add mapStateToProps into this.state?
I want filter my data on client side.
If I dispatch my action I was make request to my server, it does not have to be done, because we have all list in our store.
You can achieve this by updating component's state once a redux store is updated (say, dispatched fetch success action) and component receives new values.
However, this is not idiomatic in most cases, since now you have to deal with possible desync of local component state and external redux state (which can be updated from outside of our component AND these new values will be passed down to our component).
Of course, you can ignore those changes and do nothing on props change.
Or reverse - update the state of component every time new props arrive.
Here is an example of syncing props and state:
// get `value` from store and pass it as prop to our component
const mapStateToProps = state => ({ value: state.someKey.value })
class Component extends React.Component {
// new props has been passed
// this method receives new props and current state
// returns next state values, or null if no changes required
static getDerivedStateFromProps(props, state) {
// some complex comparison logic goes here
// if true, then desync happened, let's update local state
if (state.value !== props.value) {
// this value will be at `this.state.value`
return { value }
}
// else, no update needed for our state
return null
}
// initial values taken from props
state = { value: this.props.value }
// update local state
onChange = e => this.setState({ value: e.target.value })
// let's render props value as label and state values as input value
render() {
return (
<label>
Props value is {this.props.value}
<input value={this.state.value} onChange={this.onChange} />
</label>
)
}
}
Note, that getDerivedStateFromProps is a relatively new lifecycle method (similar to "old" componentWillReceiveProps) and this use case is mentioned in official docs as a possible sign of bad design.
TL;DR duplicating state is bad, especially if we have to sync it manually.

componentWillReceiveProps vs getDerivedStateFromProps

What exactly componentWillReceiveProps and getDerivedStateFromProps are subtle question for me. Because, I just came across to an issue while using getDerivedStateFromProps:
// Component
state = {
myState: []
}
// Using this method works fine:
componentWillReceiveProps(nextProps) {
this.setState({
myState: nextProps.myPropsState
})
}
// But using this method will cause the checkboxes to be readonly:
static getDerivedStateFromProps(nextProps,prevProps) {
const { myPropsState: myState } = nextProps
return {
myState
}
}
// And here's checkbox
<input type="checkbox" id={`someid`}
onChange={(e) => this.handleMethod(e, comp.myState)}
checked={myState.indexOf(comp.myState) > -1} />
React version: 16.4.1
getDerivedStateFromProps is not a direct alternative to componentWillReceiveProps, purely because of the fact that its called after every update, whether its the change in state or change in props or re-render of parent.
However whatever is the case, simply returning the state from getDerivedStateFromProps is not the right way, you need to compare the state and props before returning the value. Else with every update the state is getting reset to props and the cycle continues
As per the docs
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 <Transition> component that compares its previous and
next children to decide which of them to animate in and out.
Deriving state leads to verbose code and makes your components
difficult to think about. Make sure you’re familiar with simpler
alternatives:
If you need to perform a side effect (for example, data fetching
or an animation) in response to a change in props, use
componentDidUpdate lifecycle instead.
If you want to re-compute some data only when a prop changes, use
a memoization helper instead.
If you want to “reset” some state when a prop changes, consider
either making a component fully controlled or fully uncontrolled
with a key instead.
P.S. Note that the arguments to getDerivedStateFromProps are props and state and not nextProps and prevProps
To get into more details,
In order to make changes based on props change, we need to store prevPropsState in state, in order to detect changes. A typical implementation would look like
static getDerivedStateFromProps(props, state) {
// Note we need to store prevPropsState to detect changes.
if (
props.myPropsState !== state.prevPropsState
) {
return {
prevPropsState: state.myState,
myState: props.myPropsState
};
}
return null;
}
Finally, I resolved my issue. It was a painful debugging:
// Child Component
// instead of this
// this.props.onMyDisptach([...myPropsState])
// dispatching true value since myPropsState contains only numbers
this.props.onMyDispatch([...myPropsState, true])
This is because, I have two conditions: 1) on checkbox change (component) 2) on reset button pressed (child component)
I was needing to reset the states when reset button is pressed. So, while dispatching state to the props for reset button, I used a boolean value to know it's a change from the reset. You may use anything you like but need to track that.
Now, here in the component, I found some hints to the differences between componentWillReceiveProps and getDerivedStateFromProps after debugging the console output.
// Component
static getDerivedStateFromProps(props, state) {
const { myPropsState: myState } = props
// if reset button is pressed
const true_myState = myState.some(id=>id===true)
// need to remove true value in the store
const filtered_myState = myState.filter(id=>id!==true)
if(true_myState) {
// we need to dispatch the changes to apply on its child component
// before we return the correct state
props.onMyDispatch([...filtered_myState])
return {
myState: filtered_myState
}
}
// obviously, we need to return null if no condition matches
return null
}
Here's what I found the results of the console output:
getDerivedStateFromProps logs immediately whenever props changes
componentWillReceiveProps logs only after child propagates props changes
getDerivedStateFromProps doesn't respond to the props changes ( I meant for the dispatch changes as in the example code)
componentWillReceiveProps responds to the props changes
Thus, we needed to supply the changes to child component while using getDerivedStateFromProps.
The process of pasting true value in the state I require because getDerivedStateFromProps handle all the changes unlike componentWillReceiveProps handles only the child component dispatches the changes to the props.
By the way, you may use custom property to check if it is changed and update the value if getDerivedStateFromProps but for some reason I have to tweak this technique.
There might be some confusion on my wording but I hope you'll get it.
From the react docs:
Note that this method is fired on every render, regardless of the cause. This is in contrast to UNSAFE_componentWillReceiveProps, which only fires when the parent causes a re-render and not as a result of a local setState.
You are effectively overriding your state with the current props every time after calling setState(). So when you check a box (e) => this.handleMethod(e, comp.myState) is called which is assume calls setState() to update the checked state of the checkbox. But after that getDerivedStateFromProps() will be called (before render) that reverts that change. This is why unconditionally updating state from props is considered an anti-pattern.

componentDidUpdate vs componentWillReceiveProps use case in react

This is how we use componentWillReceiveProps
componentWillReceiveProps(nextProps) {
if(nextProps.myProp !== this.props.myProps) {
// nextProps.myProp has a different value than our current prop
}
}
It's very similar to componentDidUpdate
componentDidUpdate(prevProps) {
if(prevProps.myProps !== this.props.myProp) {
// this.props.myProp has a different value
// ...
}
}
I can see some differences, like if I do setState in componentDidUpdate, render will trigger twice, and the argument for componentWillReceiveProps is nextProps, while argument for componentDidUpdate is prevProp, but seriously I don't know when to use them. I often use componentDidUpdate, but with prevState, like change a dropdown state and call api
eg.
componentDidUpdate(prevProps, prevState) {
if(prevState.seleted !== this.state.seleted) {
this.setState({ selected: something}, ()=> callAPI())
}
}
The main difference between the two is:
When they are called in a component's lifecycle
How it updates component state
When are they called?
As the names suggest – and as you probably know since you mentioned "if I do setState in componentDidUpdate, render will trigger twice" – componentDidUpdate is called after the component updates (received new props or state). This is why the parameters to this function is prevProps and prevState.
So if you wanted to do something before the component received new props, you'd use componentWillReceiveProps, and if you wanted to do something after it received new props or state, you'd use componentDidUpdate.
How do they update state?
The main difference here is:
componentWillReceiveProps will update state synchronously
componentDidUpdate will update state asynchronously.
This can be important as there are some gotchya's that can come up when trying to sync state with other parts of your component's props.

Can't use state value as props for child component

In my react js app, I can't seem to use the state value as props for the child component.
In the parent component, constructor, the app has a null state called selectedWarehouseID.
This state(selectedWarehouseID) should update with some information in the componentWillMount() method.
Now, in the render method of the parent component I am embedding another child component that has this state as a props.
<ItemChooser
productsByWarehouse = { this.state.selectedWarehouseID }
/>
Now here's the problem, in the child component, the value of this.props.productsByWarehouse is always null. I found a nice explanation of why this happens, but how to wait for the data that updates in the parents componentWillMount() to access the updated value of the state that being passed in the child component props?
Possible solutions are:
1. You are storing the props value in state of child component so, Update the state of child component whenever any change happen to props values (state of parent). For that use componentWillReceiveProps lifecycle method.
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.
Like this:
componentWillReceiveProps(newProps){
this.setState({
value: newProps.productsByWarehouse
});
}
Note: Replace value by the actual key in which you are storing the value.
2. Don't store the props values in state of child component, directly use this.props.productsByWarehouse, Whenever you change the parent state values, child will automatically get the updated value in props.

Categories