Take an action on props change in ReactJS - javascript

I need to use current props and previous props value in my React component.
So i did it like this
state = {
current: null,
previous: null,
};
componentWillReceiveProps(nextProps) {
if (nextProps.amount !== this.state.current) {
this.setState({previous: this.state.current, current: nextProps.amount});
}
}
...
render() {
const {previous, current} = this.state;
return (
...
<CountUp className="counter" start={previous} end={current} duration={1}/>
...
)
}
It works fine, but is it good React practise to do it like this? Are there others "good" ways to do it?

As of v16.2.0, componentWillReceiveProps is the right place to update state, based on prop changes and since you want to use both current state and previous state in render, you need to maintain, two different state variables as you are doing
However, when you update the state based on previous state, use functional setState method
Check this answer for more details
When to use functional setState
componentWillReceiveProps(nextProps) {
if (nextProps.amount !== this.state.current) {
this.setState(prevState => ({ previous: prevState.current, current: nextProps.amount }));
}
}
According to the latest RFC to React
State derived from props/state
The purpose of this pattern is to calculate some values derived from props for use during render.
Typically componentWillReceiveProps is used for this, although if the calculation is fast enough it could just be done in render.:
From v16.3.0 onwards, you would make use of
static getDerivedStateFromProps(nextProps, prevState) {
if (
!prevState ||
prevState.current !== nextProps.amount
) {
return {
previous: prevState.current,
current: nextProps.amount
};
}
}

I'd like to update this answer for anyone else who comes here from Google. As of v16.8.6, componentWillReceiveProps has been marked as legacy, and is not recommended to use. Instead you should use componentDidUpdate and update your state based on the new props and previous props/previous state.
componentDidUpdate(prevProps, prevState) {
if (this.props.someVal !== prevState.someVal) {
this.setState({ previous: prevState.someVal, current: this.props.someVal });
}
}
Obviously, whether you check the previous state or the previous props is up to your discretion/situation. You can implement componentDidUpdate with or without the parameters, but if you want prevState you must declare prevProps.
React Update Lifecycle
componentDidUpdate()

You can use an arrow function in your setState object.
Like this :
this.setState((prevState) => {
return { yourState: prevState.yourState }
})
prevState is a default name but you can replace the name as you want

Related

post setState evaluation of state

In a react component, I update state in various ways, but I would like to do an evaluation (call a function) after the state was updated.
When I do the following, secondUpdate() does not access to the updated state, so it is one cycle late:
firstUpdate = e => {
this.setState({ email: e.state.value });
// ... some validation
secondUpdate();
}
secondUpdate() {
const allValid= this.state.aIsValid & this.state.bIsValid & this.state.cIsValid;
this.setState({ allIsValid: allValid });
}
How should I bind secondUpdate() to any or some state update?
You can use setState callback :
this.setState({ email: e.state.value } , () => {
// Will get called once the state is updated
// ... some valdidations
secondUpdate();
});
allIsValid shall not be a state at all. By having states that depend on each other, you risk that states get out of sync, and your logic breaks. Instead, as allIsValid is derived from other states, it can just be calculated based on the state inside render:
render() {
const { email } = this.state;
const allIsValid = email.length > 5 && /*...*/;
// ...
}
You can use componentDidUpdate().
componentDidUpdate(prevProps, prevState){
// code from secondUpdate()
}
Here is official doc
You might like to go through this:
https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous
Because this.props and this.state may be updated asynchronously, you
should not rely on their values for calculating the next state.
To fix it, use a second form of setState() that accepts a function
rather than an object. That function will receive the previous state
as the first argument, and the props at the time the update is applied
as the second argument:
this.setState({
// change the state
}, () => {
//Perform something after the state is updated.
})

Mutating nested object/arrays as states in React [duplicate]

This question already has answers here:
Whats the best way to update an object in an array in ReactJS?
(4 answers)
Closed 4 years ago.
I am using React components which look like this (a simplified version of the components I used, below).
My question is: how to make the same but using this.setState?
The below code works, but I am mutating the state directly, and I am receiving the following warning:
Do not mutate state directly. Use setState()
class App extends Component {
constructor(props) {
super(props);
this.state = {
playerState: [
{
name: 'Jack',
hp: 30
},{
name: 'Alan',
hp: 28
}
],
};
}
lowerPlayerHealth = (index) => () => {
this.state.playerState[index].hp = this.state.playerState[index].hp - 1
this.forceUpdate();
}
render() {
return (
<div className="App">
<p>Player 1: {this.state.playerState[0].name}</p>
<p>Health: {this.state.playerState[0].hp}</p>
<button onClick={this.lowerPlayerHealth(0)}>Hit player 1</button>
<p>Player 2: {this.state.playerState[1].name}</p>
<p>Health: {this.state.playerState[1].hp}</p>
<button onClick={this.lowerPlayerHealth(1)}>Hit player 2</button>
</div>
);
}
}
When rendered, it looks like this:
If you want to modify an existing value in the state, you should never get the value directly from the state and update the state object, but rather use the updater function in setState so you can guarantee the state values are the ones you need at the time of updating the state. This is just how React's state works and it's a very common React mistake.
From the official docs
setState() does not always immediately update the component. It may
batch or defer the update until later. This makes reading this.state
right after calling setState() a potential pitfall. Instead, use
componentDidUpdate or a setState callback (setState(updater,
callback)), either of which are guaranteed to fire after the update
has been applied. If you need to set the state based on the previous
state, read about the updater argument below.
setState() will always lead to a re-render unless
shouldComponentUpdate() returns false. If mutable objects are being
used and conditional rendering logic cannot be implemented in
shouldComponentUpdate(), calling setState() only when the new state
differs from the previous state will avoid unnecessary re-renders.
The first argument is an updater function with the signature:
(state, props) => stateChange
state is a reference to the component state at the time the change is
being applied. It should not be directly mutated. Instead, changes
should be represented by building a new object based on the input from
state and props.
Both state and props received by the updater function are guaranteed
to be up-to-date. The output of the updater is shallowly merged with
state.
So you must get the value exactly when you want to update the component inside the setState function using the first argument of the updater function.
lowerPlayerHealth = (index) => () => {
// use setState rather than mutating the state directly
this.setState((state, props) => {
// state here is the current state. Use it to update the current value found in state and ensure that it will be set correctly
return (state); // update state ensuring the correct values
});
}
Solution
To lower a value found in state:
class App extends React.Component {
constructor(props){
super(props);
this.state = {
playerState: [
{
name: 'Jack',
hp: 30
}, {
name: 'Alan',
hp: 28
}
],
};
}
lowerPlayerHealth = (index) => () => {
this.setState((state, props) => {
state.playerState[index].hp -=1; //update the current value found in state
return (state); // update state ensuring the correct values
});
}
render() {
return (
<div className="App">
<p>Player 1: {this.state.playerState[0].name}</p>
<p>Health: {this.state.playerState[0].hp}</p>
<button onClick={this.lowerPlayerHealth(0)}>Hit player 1</button>
<p>Player 2: {this.state.playerState[1].name}</p>
<p>Health: {this.state.playerState[1].hp}</p>
<button onClick={this.lowerPlayerHealth(1)}>Hit player 2</button>
</div>
);
}
}
You've answered your own question: don't mutate state. Also, best practice suggests using the function version of setState.
Since playerState is an array, use Array.map to create a new array containing the same objects, replacing only the one you want to change:
lowerPlayerHealth = (indexToUpdate) => () => {
this.setState(state => ({
...state,
playerState: state.playerState.map(
(item, index) => index === indexToUpdate
? {
...item,
hp: item.hp - 1
}
: oldItem
)
}));
}
If you made playerState an object instead of an array, you can make it tidier:
lowerPlayerHealth = (indexToUpdate) => () => {
this.setState(state => ({
...state,
playerState: {
...state.playerState,
[indexToUpdate]: {
...state.playerState[indexToUpdate],
hp: state.playerState[idToindexToUpdatepdate].hp - 1
}
}
}));
}

React fetch data getting lost

I have a component in which I fetch data based on an item ID that was clicked earlier. The fetch is successful and console.log shows the correct data, but the data gets lost with this.setState. I have componentDidUpdate and componentDidMount in the same component, not sure if this is okay or maybe these two are messing eachother up?
Here is the code:
const teamAPI = 'http://localhost:8080/api/teams/'
const playerAPI = 'http://localhost:8080/api/playersByTeam/'
const matchAPI = 'http://localhost:8080/api/match/'
class View extends Component {
constructor() {
super();
this.state = {
data: [],
playersData: [],
update: [],
team1: [],
team2: [],
matchData: [],
testTeam: [],
};
}
componentDidUpdate(prevProps) {
if (prevProps.matchId !== this.props.matchId) {
fetch(matchAPI + this.props.matchId)
.then((matchResponse) => matchResponse.json())
.then((matchfindresponse) => {
console.log(matchfindresponse);
this.setState({
matchData:matchfindresponse,
testTeam:matchfindresponse.team1.name,
})
})
}
}
componentDidMount() {
fetch(teamAPI)
.then((Response) => Response.json())
.then((findresponse) => {
console.log(findresponse)
this.setState({
data:findresponse,
team1:findresponse[0].name,
team2:findresponse[1].name,
})
})
fetch(playerAPI + 82)
.then(playerResponse => playerResponse.json())
.then(players => {
console.log(players)
this.setState({
playersData:players
})
})
}
The first render also gives this warning:
Warning: Can only update a mounted or mounting component. This usually means you called setState, replaceState, or forceUpdate on an unmounted component. This is a no-op.
Please check the code for the View component.
Everything from ComponentDidMount works fine in render but {this.state.matchData} and {this.state.testTeam} from componentDidUpdate are empty.
Could the problem be that ComponentDidMount re-renders the component which causes the data from ComponentDidUpdate to be lost and if so, how could I fix this?
Tried ComponentWillReceiveProps like this but still no luck
componentWillReceiveProps(newProps) {
if (newProps.matchId !== this.props.matchId) {
fetch(matchAPI + newProps.matchId)
.then((matchResponse) => matchResponse.json())
.then((matchfindresponse) => {
console.log(matchfindresponse.team1.name);
console.log(this.props.matchId + ' ' + newProps.matchId);
this.setState({
matchData:matchfindresponse.team1.name,
})
})
}
}
On your componentDidMount you should be using Promise.all. This isn't really your problem, but it does make more sense.
componentDidMount() {
const promises = [
fetch(teamAPI).then(resp => resp.json()),
fetch(playerAPI + 82).then(resp => resp.json())
];
Promise.all(promises).then(([teamData, playerData]) => {
// you can use this.setState once here
});
}
Looks like your componentDidUpdate should be a getDerivedStateFromProps in combination with componentDidUpdate (this is new to react 16.3 so if you are using an older version use the depreciated componentWillReceiveProps). Please see https://github.com/reactjs/rfcs/issues/26. Notice too that now componentDidUpdate receives a third parameter from getDerivedStateFromProps. Please see https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html for more details.
EDIT: Just to add more details.
Your state object should just include other key like matchIdChanged.
Then
// in your state in your constructor add matchId and matchIdChanged then
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.matchId !== prevState.matchId) {
return { matchIdChanged: true, matchId: nextProps.matchId }
}
return null;
}
componentDidUpdate() {
if (this.state.matchIdChanged) {
fetch(matchAPI + this.props.matchId)
.then((matchResponse) => matchResponse.json())
.then((matchfindresponse) => {
console.log(matchfindresponse);
this.setState({
matchData:matchfindresponse,
testTeam:matchfindresponse.team1.name,
matchIdChanged: false // add this
})
})
}
}
instead of using componentDidUpdate() lifecycle hook of react try using getDerivedStateFromProps() lifecycle function if you are using react 16.3, else try using componentWillReceiveProps() for below versions. In my opinion try to avoid the use of componentDidUpdate().
Plus error you are getting is because, setState() function is called, when your component somehow gets unmounted, there can be multiple reasons for this, most prominent being -
check the render function of this component, are you sending null or something, based on certain condition?
check the parent code of component, and see when is the component getting unmounted.
Or you can share these code, so that we might help you with this.
Plus try to debug using ComponentWillUnmount(), put console.log() in it and test it for more clarity.
Hope this helps, thanks

Reactjs - Setting State from props using setState in child component

I'm having the following class that renders users based on a sort dropdown. Users will be listed in alphabetical order if i choose "alphabetical" and in group order when i choose "group".
render(){
return(
const {members, sort} = this.state
{ sort === "alphabetical" && <SortByAlphabet members={members} /> }
{ sort === "group" && <SortByGroup members={members}/> }
)
)
In <SortByAlphabet /> component I am setting a component state object from props.members in componentWillReceiveProps() function.
componentWillReceiveProps = props => {
this.setState({ members : props.members });
}
When I choose "group" sort, <SortByAlphabet /> component is unmounting and <SortByGroup /> is mounting in the DOM. Again when i switch back to "alphabetical" sort, the state variable (members) that was set previosly in <SortByAlphabet /> component becomes NULL because the component was removed from the DOM.
componentWillReceiveProps function is not triggering the second time when re-rendering <SortByAlphabet /> b'coz the props didn't change. But i want to update the state variable like i did it for the first time in componentWillReceiveProps function.
How to do that?
componentWillMount is called only once in the component lifecycle, immediately before the component is rendered. It is usually used to perform any state changes needed before the initial render, because calling this.setState in this method will not trigger an additional render
So you can update your staate using
componentWillMount ()
{
this.setState({ members : props.members });
}
As #Vikram also said, componentWillReceiveProps is not called for the first time, so when your component is initially mounted your state is not getting set, so you need to set the state with props in the componentWillMount/componentDidMount function(which are called only on the first render) also along with the componentWillReceiveProps function
componentWillReceiveProps = props => {
if(props.members !== this.props.members) {
this.setState({ members : props.members });
}
}
componentWillMount() {
this.setState({ members : this.props.members });
}
From version 16.3.0 onwards, you would make use of getDerivedStateFromProps method to update the state in response to props change,
getDerivedStateFromProps is invoked after a component is instantiated
as well as when it receives new props. It should return an object to
update state, or null to indicate that the new props do not require
any state updates.
static getDerivedStateFromProps(nextProps, prevState) {
if(nextProps.members !== prevState.memebers) {
return { members: nextProps.members };
}
return null;
}
EDIT:
There has been a change in getDerivedStateFromProps API from v16.4 where it receives props, state as arguments and is called on every update along with initial render. In such a case, you can either trigger a new mount of the component by changing the key
<SortByAlphabet key={members} />
and in SortByAlphabet have
componentWillMount() {
this.setState({ members : this.props.members });
}
or use getDerivedStateFromProps like
static getDerivedStateFromProps(props, state) {
if(state.prevMembers !== props.members) {
return { members: nextProps.members, prevMembers: props.members };
}
return { prevMembers: props.members };
}

this.setState isn't merging states as I would expect

I have the following state:
this.setState({ selected: { id: 1, name: 'Foobar' } });
Then I update the state:
this.setState({ selected: { name: 'Barfoo' }});
Since setState is suppose to merge I would expect it to be:
{ selected: { id: 1, name: 'Barfoo' } };
But instead it eats the id and the state is:
{ selected: { name: 'Barfoo' } };
Is this expected behavior and what's the solution to update only one property of a nested state object?
I think setState() doesn't do recursive merge.
You can use the value of the current state this.state.selected to construct a new state and then call setState() on that:
var newSelected = _.extend({}, this.state.selected);
newSelected.name = 'Barfoo';
this.setState({ selected: newSelected });
I've used function _.extend() function (from underscore.js library) here to prevent modification to the existing selected part of the state by creating a shallow copy of it.
Another solution would be to write setStateRecursively() which does recursive merge on a new state and then calls replaceState() with it:
setStateRecursively: function(stateUpdate, callback) {
var newState = mergeStateRecursively(this.state, stateUpdate);
this.replaceState(newState, callback);
}
Immutability helpers were recently added to React.addons, so with that, you can now do something like:
var newState = React.addons.update(this.state, {
selected: {
name: { $set: 'Barfoo' }
}
});
this.setState(newState);
Immutability helpers documentation.
Since many of the answers use the current state as a basis for merging in new data, I wanted to point out that this can break. State changes are queued, and do not immediately modify a component's state object. Referencing state data before the queue has been processed will therefore give you stale data that does not reflect the pending changes you made in setState. From the docs:
setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value.
This means using "current" state as a reference in subsequent calls to setState is unreliable. For example:
First call to setState, queuing a change to state object
Second call to setState. Your state uses nested objects, so you want to perform a merge. Before calling setState, you get current state object. This object does not reflect queued changes made in first call to setState, above, because it's still the original state, which should now be considered "stale".
Perform merge. Result is original "stale" state plus new data you just set, changes from initial setState call are not reflected. Your setState call queues this second change.
React processes queue. First setState call is processed, updating state. Second setState call is processed, updating state. The second setState's object has now replaced the first, and since the data you had when making that call was stale, the modified stale data from this second call has clobbered the changes made in the first call, which are lost.
When queue is empty, React determines whether to render etc. At this point you will render the changes made in the second setState call, and it will be as though the first setState call never happened.
If you need to use the current state (e.g. to merge data into a nested object), setState alternatively accepts a function as an argument instead of an object; the function is called after any previous updates to state, and passes the state as an argument -- so this can be used to make atomic changes guaranteed to respect previous changes.
I didn't want to install another library so here's yet another solution.
Instead of:
this.setState({ selected: { name: 'Barfoo' }});
Do this instead:
var newSelected = Object.assign({}, this.state.selected);
newSelected.name = 'Barfoo';
this.setState({ selected: newSelected });
Or, thanks to #icc97 in the comments, even more succinctly but arguably less readable:
this.setState({ selected: Object.assign({}, this.state.selected, { name: "Barfoo" }) });
Also, to be clear, this answer doesn't violate any of the concerns that #bgannonpl mentioned above.
Preserving the previous state based on #bgannonpl answer:
Lodash example:
this.setState((previousState) => _.merge({}, previousState, { selected: { name: "Barfood"} }));
To check that it's worked properly, you can use the second parameter function callback:
this.setState((previousState) => _.merge({}, previousState, { selected: { name: "Barfood"} }), () => alert(this.state.selected));
I used merge because extend discards the other properties otherwise.
React Immutability example:
import update from "react-addons-update";
this.setState((previousState) => update(previousState, {
selected:
{
name: {$set: "Barfood"}
}
});
As of right now,
If the next state depends on the previous state, we recommend using
the updater function form, instead:
according to documentation https://reactjs.org/docs/react-component.html#setstate, using:
this.setState((prevState) => {
return {quantity: prevState.quantity + 1};
});
My solution for this kind of situation is to use, like another answer pointed out, the Immutability helpers.
Since setting the state in depth is a common situation, I've created the folowing mixin:
var SeStateInDepthMixin = {
setStateInDepth: function(updatePath) {
this.setState(React.addons.update(this.state, updatePath););
}
};
This mixin is included in most of my components and I generally do not use setState directly anymore.
With this mixin, all you need to do in order to achieve the desired effect is to call the function setStateinDepth in the following way:
setStateInDepth({ selected: { name: { $set: 'Barfoo' }}})
For more information:
On how mixins work in React, see the official documentation
On the syntax of the parameter passed to setStateinDepth see the Immutability Helpers documentation.
I am using es6 classes, and I ended up with several complex objects on my top state and was trying to make my main component more modular, so i created a simple class wrapper to keep the state on the top component but allow for more local logic.
The wrapper class takes a function as its constructor that sets a property on the main component state.
export default class StateWrapper {
constructor(setState, initialProps = []) {
this.setState = props => {
this.state = {...this.state, ...props}
setState(this.state)
}
this.props = initialProps
}
render() {
return(<div>render() not defined</div>)
}
component = props => {
this.props = {...this.props, ...props}
return this.render()
}
}
Then for each complex property on the top state, i create one StateWrapped class. You can set the default props in the constructor here and they will be set when the class is initialised, you can refer to the local state for values and set the local state, refer to local functions, and have it passed up the chain:
class WrappedFoo extends StateWrapper {
constructor(...props) {
super(...props)
this.state = {foo: "bar"}
}
render = () => <div onClick={this.props.onClick||this.onClick}>{this.state.foo}</div>
onClick = () => this.setState({foo: "baz"})
}
So then my top level component just needs the constructor to set each class to it's top level state property, a simple render, and any functions that communicate cross-component.
class TopComponent extends React.Component {
constructor(...props) {
super(...props)
this.foo = new WrappedFoo(
props => this.setState({
fooProps: props
})
)
this.foo2 = new WrappedFoo(
props => this.setState({
foo2Props: props
})
)
this.state = {
fooProps: this.foo.state,
foo2Props: this.foo.state,
}
}
render() {
return(
<div>
<this.foo.component onClick={this.onClickFoo} />
<this.foo2.component />
</div>
)
}
onClickFoo = () => this.foo2.setState({foo: "foo changed foo2!"})
}
Seems to work quite well for my purposes, bear in mind though you can't change the state of the properties you assign to wrapped components at the top level component as each wrapped component is tracking its own state but updating the state on the top component each time it changes.
Solution
Edit: This solution used to use spread syntax. The goal was make an object without any references to prevState, so that prevState wouldn't be modified. But in my usage, prevState appeared to be modified sometimes. So, for perfect cloning without side effects, we now convert prevState to JSON, and then back again. (Inspiration to use JSON came from MDN.)
Remember:
State updates are shallow: they only go one level deep
State shouldn't be directly mutated. Instead, use setState(prevState => stateChange)
Steps
Make a copy of the root-level property of state that you want to change
Mutate this new object
Create an update object
Return the update
Steps 3 and 4 can be combined on one line.
Example
this.setState(prevState => {
var newSelected = JSON.parse(JSON.stringify(prevState.selected)) //1
newSelected.name = 'Barfoo'; //2
var update = { selected: newSelected }; //3
return update; //4
});
Simplified example:
this.setState(prevState => {
var selected = JSON.parse(JSON.stringify(prevState.selected)) //1
selected.name = 'Barfoo'; //2
return { selected }; //3, 4
});
This follows the React guidelines nicely. Based on eicksl's answer to a similar question.
ES6 solution
We set the state initially
this.setState({ selected: { id: 1, name: 'Foobar' } });
//this.state: { selected: { id: 1, name: 'Foobar' } }
We are changeing a property on some level of the state object:
const { selected: _selected } = this.state
const selected = { ..._selected, name: 'Barfoo' }
this.setState({selected})
//this.state: { selected: { id: 1, name: 'Barfoo' } }
React state doesn't perform the recursive merge in setState while expects that there won't be in-place state member updates at the same time. You either have to copy enclosed objects/arrays yourself (with array.slice or Object.assign) or use the dedicated library.
Like this one. NestedLink directly supports handling of the compound React state.
this.linkAt( 'selected' ).at( 'name' ).set( 'Barfoo' );
Also, the link to the selected or selected.name can be passed everywhere as a single prop and modified there with set.
have you set the initial state?
I'll use some of my own code for example:
getInitialState: function () {
return {
dragPosition: {
top : 0,
left : 0
},
editValue : "",
dragging : false,
editing : false
};
}
In an app I'm working on, this is how I've been setting and using state. I believe on setState you can then just edit whatever states you want individually I've been calling it like so:
onChange: function (event) {
event.preventDefault();
event.stopPropagation();
this.setState({editValue: event.target.value});
},
Keep in mind you have to set the state within the React.createClass function that you called getInitialState
I use the tmp var to change.
changeTheme(v) {
let tmp = this.state.tableData
tmp.theme = v
this.setState({
tableData : tmp
})
}

Categories