What is the correct way of updating state, in a nested object, in React with Hooks?
export Example = () => { const [exampleState, setExampleState] = useState( {masterField: { fieldOne: "a", fieldTwo: { fieldTwoOne: "b" fieldTwoTwo: "c" } } })
How would one use setExampleState
to update exampleState
to a
(appending an field)?
const a = { masterField: { fieldOne: "a", fieldTwo: { fieldTwoOne: "b", fieldTwoTwo: "c" } }, masterField2: { fieldOne: "c", fieldTwo: { fieldTwoOne: "d", fieldTwoTwo: "e" } }, } }
b
(Changing values)?
const b = {masterField: { fieldOne: "e", fieldTwo: { fieldTwoOne: "f" fieldTwoTwo: "g" } } })
Answers:
Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.
Method 1
You can pass new value like this:
setExampleState({...exampleState, masterField2: { fieldOne: "a", fieldTwo: { fieldTwoOne: "b", fieldTwoTwo: "c" } }, })
Method 2
If anyone is searching for useState() hooks update for object
- Through Input const [state, setState] = useState({ fName: "", lName: "" }); const handleChange = e => { const { name, value } = e.target; setState(prevState => ({ ...prevState, [name]: value })); }; <input value={state.fName} type="text" onChange={handleChange} name="fName" /> <input value={state.lName} type="text" onChange={handleChange} name="lName" /> *************************** - Through onSubmit or button click setState(prevState => ({ ...prevState, fName: 'your updated value here' }));
Method 3
Generally you should watch out for deeply nested objects in React state. To avoid unexpected behavior, the state should be updated immutably. When you have deep objects, you end up deep cloning them for immutability, which can be quite expensive in React. Why?
Once you deep clone the state, React will recalculate and re-render everything that depends on the variables, even though they haven’t changed!
So, before trying to solve your issue, think how you can flatten the state first. As soon as you do that, you will find handy tools that will help dealing with large states, such as useReducer().
In case you thought about it, but are still convinced you need to use a deeply nested state tree, you can still use useState() with libraries like immutable.js and Immutability-helper. They make it simple to update or clone deep objects without having to worry about mutability.
Method 4
I’m late to the party.. 🙂
@aseferov answer works very well when the intention is to re-enter the entire object structure. However, if the target/goal is to update a specific field value in an Object, I believe the approach below is better.
situation:
const [infoData, setInfoData] = useState({ major: { name: "John Doe", age: "24", sex: "M", }, minor:{ id: 4, collegeRegion: "south", } });
Updating a specific record will require making a recall to the previous State prevState
Here:
setInfoData((prevState) => ({ ...prevState, major: { ...prevState.major, name: "Tan Long", } }));
perhaps
setInfoData((prevState) => ({ ...prevState, major: { ...prevState.major, name: "Tan Long", }, minor: { ...prevState.minor, collegeRegion: "northEast" }));
I hope this helps anyone trying to solve a similar problem.
Method 5
It’s possible to use useReducer
hook for managing complex state, instead of useState
. To do it, first initialize state and updating function like this:
const initialState = { name: "Bob", occupation: "builder" }; const [state, updateState] = useReducer( (state, updates) => ({ ...state, ...updates, }), initialState );
And then you can update the state by only passing partial updates, like this:
updateState({ ocupation: "postman" })
Method 6
Thanks Philip this helped me – my use case was I had a form with lot of input fields so I maintained initial state as object and I was not able to update the object state.The above post helped me 🙂
const [projectGroupDetails, setProjectGroupDetails] = useState({ "projectGroupId": "", "projectGroup": "DDD", "project-id": "", "appd-ui": "", "appd-node": "" }); const inputGroupChangeHandler = (event) => { setProjectGroupDetails((prevState) => ({ ...prevState, [event.target.id]: event.target.value })); } <Input id="projectGroupId" labelText="Project Group Id" value={projectGroupDetails.projectGroupId} onChange={inputGroupChangeHandler} />
Method 7
In 2022
If you are looking for the same functionality as this.setState
( came from the class components
) in functional components
then this is the answer that helps you a lot.
For Example
You have a state like below and want to update the specific field only from the whole state then you need to use the object destructing
every time and sometimes it will be irritating.
const [state, setState] = useState({first: 1, second: 2}); // results will be state = {first: 3} instead of {first: 3, second: 2} setState({first: 3}) // To resolve that you need to use object destructing every time // results will be state = {first: 3, second: 2} setState(prev => ({...prev, first: 3}))
To solve that I came up with the useReducer
approach. Please check useReducer.
const stateReducer = (state, action) => ({
...state,
...(typeof action === 'function' ? action(state) : action),
});
const [state, setState] = useReducer(stateReducer, {first: 1, second: 2}); // results will be state = {first: 3, second: 2} setState({first: 3}) // you can also access the previous state callback if you want // results will remain same, state = {first: 3, second: 2} setState(prev => ({...prev, first: 3}))
You can store that stateReducer
in utils
files and import it in every file if you want.
Here is the custom hook
if you want.
import React from 'react'; export const stateReducer = (state, action) => ({ ...state, ...(typeof action === 'function' ? action(state) : action), }); const useReducer = (initial, lazyInitializer = null) => { const [state, setState] = React.useReducer(stateReducer, initial, init => lazyInitializer ? lazyInitializer(init) : init ); return [state, setState]; }; export default useReducer;
Typescript
import React, { Dispatch } from "react"; type SetStateAction<S> = S | ((prev: S) => S); type STATE<R> = [R, Dispatch<SetStateAction<Partial<R>>>]; const stateReducer = (state, action) => ({ ...state, ...(typeof action === "function" ? action(state) : action), }); const useReducer = <S>(initial, lazyInitializer = null): STATE<S> => { const [state, setState] = React.useReducer(stateReducer, initial, (init) => lazyInitializer ? lazyInitializer(init) : init, ); return [state, setState]; }; export default useReducer;
Method 8
You have to use Rest parameters and spread syntax (https://javascript.info/rest-parameters-spread) AND set a function with preState as the argument of the setState.
Does not work (missing function)
[state, setState] = useState({}) const key = 'foo'; const value = 'bar'; setState({ ...state, [key]: value });
Does work!
[state, setState] = useState({}) const key = 'foo'; const value = 'bar'; setState(prevState => ({ ...prevState, [key]: value }));
Method 9
I have given both Append, Whole object update, Specific key update examples for the solve
Append and modify both can be done by a simple step. I think this is more stable and safe which has no immutable or mutable dependency.
This is how you can append new object
setExampleState(prevState => ({
...prevState,
masterField2: {
fieldOne: "c",
fieldTwo: {
fieldTwoOne: "d",
fieldTwoTwo: "e"
}
},
}))
Suppose you want to modify again the masterField2 object. There can be two situation. You want to update the whole object or update a specific key of the object.
Update the whole object – So here the whole value for key masterField2 will be updated.
setExampleState(prevState => ({
...prevState,
masterField2: {
fieldOne: "c",
fieldTwo: {
fieldTwoOne: "d",
fieldTwoTwo: "e"
}
},
}))
But what if you wanted to change only fieldTwoOne key inside the masterField2 object. You do the following.
let oldMasterField2 = exampleState.masterField2
oldMasterField2.fieldTwo.fieldTwoOne = 'changed';
setExampleState(prevState => ({
...prevState,
masterField2: oldMasterField2
}))
Method 10
function App() { const [todos, setTodos] = useState([ { id: 1, title: "Selectus aut autem", completed: false }, { id: 2, title: "Luis ut nam facilis et officia qui", completed: false }, { id: 3, title: "Fugiat veniam minus", completed: false }, { id: 4, title: "Aet porro tempora", completed: true }, { id: 5, title: "Laboriosam mollitia et enim quasi", completed: false } ]); const changeInput = (e) => {todos.map(items => items.id === parseInt(e.target.value) && (items.completed = e.target.checked)); setTodos([...todos], todos);} return ( <div className="container"> {todos.map(items => { return ( <div key={items.id}> <label> <input type="checkbox" onChange={changeInput} value={items.id} checked={items.completed} /> {items.title}</label> </div> ) })} </div> ); }
Method 11
Initially I used object in useState, but then I moved to useReducer hook for complex cases. I felt a performance improvement when I refactored the code.
useReducer is usually preferable to useState when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one.
I already implemented such hook for my own use:
/**
* Same as useObjectState but uses useReducer instead of useState
* (better performance for complex cases)
* @param {*} PropsWithDefaultValues object with all needed props
* and their initial value
* @returns [state, setProp] state - the state object, setProp - dispatch
* changes one (given prop name & prop value) or multiple props (given an
* object { prop: value, ...}) in object state
*/
export function useObjectReducer(PropsWithDefaultValues) {
const [state, dispatch] = useReducer(reducer, PropsWithDefaultValues);
//newFieldsVal={[field_name]: [field_value], ...}
function reducer(state, newFieldsVal) {
return { ...state, ...newFieldsVal };
}
return [
state,
(newFieldsVal, newVal) => {
if (typeof newVal !== "undefined") {
const tmp = {};
tmp[newFieldsVal] = newVal;
dispatch(tmp);
} else {
dispatch(newFieldsVal);
}
},
];
}
more related hooks.
Method 12
, do it like this example :
first creat state of the objects:
const [isSelected, setSelection] = useState([{ id_1: false }, { id_2: false }, { id_3: false }]);
then change the value on of them:
// if the id_1 is false make it true or return it false. onValueChange={() => isSelected.id_1 == false ? setSelection([{ ...isSelected, id_1: true }]) : setSelection([{ ...isSelected, id_1: false }])}
Method 13
I think best solution is Immer. It allows you to update object like you are directly modifying fields (masterField.fieldOne.fieldx = ‘abc’). But it will not change actual object of course. It collects all updates on a draft object and gives you a final object at the end which you can use to replace original object.
Method 14
I leave you a utility function to inmutably update objects
/** * Inmutable update object * @param {Object} oldObject Object to update * @param {Object} updatedValues Object with new values * @return {Object} New Object with updated values */ export const updateObject = (oldObject, updatedValues) => { return { ...oldObject, ...updatedValues }; };
So you can use it like this
const MyComponent = props => { const [orderForm, setOrderForm] = useState({ specialities: { elementType: "select", elementConfig: { options: [], label: "Specialities" }, touched: false } }); // I want to update the options list, to fill a select element // ---------- Update with fetched elements ---------- // const updateSpecialitiesData = data => { // Inmutably update elementConfig object. i.e label field is not modified const updatedOptions = updateObject( orderForm[formElementKey]["elementConfig"], { options: data } ); // Inmutably update the relevant element. const updatedFormElement = updateObject(orderForm[formElementKey], { touched: true, elementConfig: updatedOptions }); // Inmutably update the relevant element in the state. const orderFormUpdated = updateObject(orderForm, { [formElementKey]: updatedFormElement }); setOrderForm(orderFormUpdated); }; useEffect(() => { // some code to fetch data updateSpecialitiesData.current("specialities",fetchedData); }, [updateSpecialitiesData]); // More component code }
If not you have more utilities here : https://es.reactjs.org/docs/update.html
Method 15
Your Object for which you want to make state
let teams = { team: [ { name: "one", id: "1" }, ] }
Making State of teams object
const [state, setState] = useState(teams);
Update State like this
setState((prevState)=>({...prevState,team:[ ...prevState.team, { name: "two", id: "2" } ]}))
After the updation State becomes
{ team: [ { name: "one", id: "1" }, { name: "two", id: "2" } ] }
To Render items according to current state use Map function
{state.team.map((curr_team) => { return ( <div> <p>{curr_team.id}</p> <p>{curr_team.name}</p> </div> ) })}
Method 16
I think a more elegant solution will be to create the updated state object, while retaining the previous values of state. The Object property which is needed to be updated can be provided in the form of array something like this –
import React,{useState, useEffect} from 'react' export default function Home2(props) { const [x, setX] = useState({name : '',add : {full : '', pin : '', d : { v : '' }}}) const handleClick = (e, type)=>{ let obj = {} if(type.length > 1){ var z = {} var z2 = x[type[0]] type.forEach((val, idx)=>{ if(idx === type.length - 1){ z[val] = e.target.value } else if(idx > 0){ Object.assign(z , z2) /*{...z2 , [val]:{} }*/ z[val] = {} z = z[val] z2 = z2[val] }else{ z = {...z2} obj = z } }) }else obj = e.target.value setX( { ...x , [type[0]] : obj } ) } return ( <div> <input value = {x.name} onChange={e=>handleClick(e,["name"])}/> <input value = {x.add.full} onChange={e=>handleClick(e,["add","full"])} /> <input value = {x.add.pin} onChange={e=>handleClick(e,["add","pin"])} /><br/> <input value = {x.add.d.v} onChange={e=>handleClick(e,["add","d","v"])} /><br/> {x.name} <br/> {x.add.full} <br/> {x.add.pin} <br/> {x.add.d.v} </div> ) }
All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0