Updating and Merging State Object Using React Usestate() Hook

Updating and merging state object using React useState() hook

Both options are valid, but just as with setState in a class component you need to be careful when updating state derived from something that already is in state.

If you e.g. update a count twice in a row, it will not work as expected if you don't use the function version of updating the state.

const { useState } = React;
function App() { const [count, setCount] = useState(0);
function brokenIncrement() { setCount(count + 1); setCount(count + 1); }
function increment() { setCount(count => count + 1); setCount(count => count + 1); }
return ( <div> <div>{count}</div> <button onClick={brokenIncrement}>Broken increment</button> <button onClick={increment}>Increment</button> </div> );}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react@16/umd/react.development.js"></script><script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>

How do state updates merge in React hooks?

Its not true what you state here:

"In Class component, the last setState action will be taken into consideration. Since the state updates are shallow merged.".

Same goes for your example with Object.assign.

The "merge" mentioned in React docs is related to merging with current state, for example:

state = { count: 0, name: 'Dennis' }

// Merged with this.state, the 'name' attribute is not removed
this.setState({count: this.state.count+1})

// the state now is { count: 1, name: 'Dennis' }

While same code, won't work with function component, since its different API, as mentioned in the docs, it doesn't merge.

const [state,setState] = useState({ count: 0, name: 'Dennis' })

// NO MERGE, you assign a new object
setState({count: state.count+1})

// the state now is { count: 1 }

Therefore, asking if the "setState call ever execute" is not related to class / function component, its a matter if the set state call is batched or not.

Does setCount in useState hook will behave the same as setState?, Will setCount(count+1) ever execute?

If the calls don't batch, the first call will be executed.

When set state calls are batched? See here.

ReactJS useState hook: Can I update array of objects using the state variable itself?

Both implementations would be considered state mutations and should be avoided as this is anti-pattern in React. React uses shallow reference equality checks as part of its Reconciliation process, i.e. if a chunk of state is the same object reference as the previous render cycle it's assumed the value is the same and rerendering is skipped.

  • Version 1

    This is just directly mutating the state object.

      list[index][someKey] = some_updated_value; // <-- mutation!
    setList(list);
  • Version 2

    This also directly mutates the state object since newList is a reference to list.

      const newList = list; // <-- newList reference to state
    newList[index][someKey] = some_updated_value; // <-- mutation!
    setList(newList);

The idea with React state updates is to apply the Immutable Update Pattern. This is where you create shallow copies of any state and nested state that is being updated. This is also usually combined with functional state updates where you pass a callback function to the updater function that is passed the previous state to update from. This helps avoid stale state enclosures.

Example:

setList(list => list.map((item, i) => // Array.protptype.map creates new array
i === index
? { // new object reference for updated item
...item, // shallow copy previous item state
[someKey]: newValue // overwrite property being updated
}
: item
));

Good docs:

  • Immutable Update Pattern - this is a Redux doc but provides a fantastic explanation and example.
  • Functional State Updates

React Hooks useState() with Object

You can pass new value like this:

  setExampleState({...exampleState,  masterField2: {
fieldOne: "a",
fieldTwo: {
fieldTwoOne: "b",
fieldTwoTwo: "c"
}
},
})

React useState overwrites state object instead of merging

useState does not merge state like setState in class component, you have to do the job on your own.

onChange={(e) => setParams((params)=>({ ...params, color: e.target.value }))}

Update object in array using useState and a button

You can update a user by first getting the index of this user in the array, then update the element in the array, then return the array :

const update = (e) => {
setUsers(users => {
const index = users.findIndex(item => item.name === user.name);

if (index === -1) {
return […users, user];
}

users[index] = user;
return users;
}
}

React useState with object update several states in object

Unlike the setState method found in class components, useState does
not automatically merge update objects. You can replicate this
behavior by combining the function updater form with object spread
syntax:

To achieve the behaviour you expect, you need to do the following:

setTeam(prevState => {
// Object.assign would also work
return {...prevState, manager: newValue, captain: newValue2 };
});

You may also want to consider using useReducer as your state has multiple sub values, useState is usually best used with single value states.

You can read more here: https://reactjs.org/docs/hooks-reference.html#usestate

How do I update an object state in react via hooks

There are a few things you can improve:

  • the react-hook useState does not behave like the class counterpart. It does not automatically merge the provided object with the state, you have to do that yourself.
  • I would recommend if you can work without an object as your state to do so as this can reduce the amount of re-renders by a significant amount and makes it easier to change the shape of the state afterwards as you can just add or remove variables and see all the usages immediately.

With a state object

export default function App() {

// Set date state
const [data,setData] = useState({
data: [],
loaded: false,
placeholder: 'Loading'
});

// Fetch and update date
useEffect(() => {
fetch('http://localhost:8000/api/lead/')
.then(response => {
if (response.status !== 200) {
throw new Error(response.statusText); // Goto catch block
}
return response.json(); // <<- Return the JSON Object
})
.then(result => {
console.log(data);
setData(oldState => ({ ...oldState, data: result})); // <<- Merge previous state with new data
})
.catch(error => { // Use .catch() to catch exceptions. Either in the request or any of your .then() blocks
console.error(error); // Log the error object in the console.
const errorMessage = 'Something went wrong';
setData(oldState=> ({ ...oldState, placeholder: errorMessage }));
});

},[]);

return (
<h1>{console.log(data)}</h1>
);
}

Without a state object

export default function App() {
const [data, setData] = useState([]);
const [loaded, setLoaded] = useState(false);
const [placeholder, setPlaceholder] = useState('Loading');

// Fetch and update date
useEffect(() => {
fetch('http://localhost:8000/api/lead/')
.then(response => {
if (response.status !== 200) {
throw new Error(response.statusText); // Goto catch block
}
return response.json(); // <<- Return the JSON Object
})
.then(result => {
console.log(data);
setData(data);
})
.catch(error => { // Use .catch() to catch exceptions. Either in the request or any of your .then() blocks
console.error(error); // Log the error object in the console.
const errorMessage = 'Something went wrong';
setPlaceholder(errorMessage);
});

},[]);

return (
<h1>{console.log(data)}</h1>
);
}

Replace an existing object in state with another object using react hooks usestate

Its not updating properly because you are directly mutating the state. You need to either make a copy of the state and change that and update the state like:

const newState = [...roomData];
newState[roomIndex] = room;
setRoomData(newState);

or you can try something like:

roomData[roomIndex] ? setRoomData(roomData.map((data,i)=>{
if(i === roomIndex){
return room
}
return data;
}) : setRoomData([...roomData, room]);


Related Topics



Leave a reply



Submit