Related
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"
}
}
})
You can pass new value like this:
setExampleState({...exampleState, masterField2: {
fieldOne: "a",
fieldTwo: {
fieldTwoOne: "b",
fieldTwoTwo: "c"
}
},
})
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'
}));
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.
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.
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({ occupation: "postman" })
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;
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}
/>
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
}));
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
}))
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>
);
}
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.
useReducer React docs
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.
, 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 }])}
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.
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>
)
})}
If you work with boolean values and arrays This can help you:
const [checkedOrders, setCheckedOrders] = useState<Record<string, TEntity>>({});
const handleToggleCheck = (entity: TEntity) => {
const _checkedOrders = { ...checkedOrders };
const isChecked = entity.id in checkedOrders;
if (isChecked) {
delete _checkedOrders[entity.id];
} else {
_checkedOrders[entity.id] = entity;
}
setCheckedOrders(_checkedOrders);
};
Answers are already there but This type isn't mentioned so take look at the example of this kind ...
const[data,setdata]= useState({
username: [
email,
"required",
//...some additional codes
],
password: [
password,
"required|password-5",
//..additional code if any..
],
})
**To update the state variable email in this example in the input field you can add similar code with your variable name **
<Input
onChangeText={(t) => setdata(prevState=>({...prevState,username:{[0]:t}}))}
value={data.username[0]}
/>
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
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>
)
}
I am creating delete functionality in my application. But somehow it is not behaving properly.
The initial state and reducer function is defined below:
import { createSlice } from '#reduxjs/toolkit';
const filterDataTemplate = {
programId: '',
year: '',
};
const initialState = {
//some other state
filterData: { ...filterDataTemplate },
};
const slice = createSlice({
name: 'editFilterSlice',
initialState: initialState,
reducers: {
updateFilterProgramId: (state, action) => {
return {
...state,
filterData: {
...state.filterData,
programId: action.payload,
},
};
},
updateFilterYear: (state, action) => {
return {
...state,
filterData: {
...state.filterData,
year: action.payload,
},
};
},
},
});
export const {
updateFilterYear,
updateFilterProgramId,
} = slice.actions;
export default slice.reducer;
And filter details is the object containing year and programId:
filterDetails: {year:2021, programId: "Ameria"}
And i want to achieve
filterDetails: {year:"", programId: ""}
So delete handler is stated below to achieve above criteria:
const handleDelete = (e) => {
e.preventDefault();
if (//some condition) {
dispatch(updateFilterYear(''));
console.log('filterdetails', filterDetails);
//filterDetails: {year:2021, programId: "Ameria"}
} else {
dispatch(updateFilterProgramId(''));
}
}.
//Outside handleDelete function
console.log('filterdetails', filterDetails);
//filterDetails: {year:"", programId: "Ameria"}
So filter details containg year and programId is obtained with the help of this code:
const filterDetails = useAppSelector(
(state) => state.locationsFilter.filterData
);
handleDelete function is getting called properly when I am clicking a button
But after running this code, the filter details is not updating first time but when I am again running the handler second time it is updating.
And outside handleDelete it is showing correct value in the console but inside handleDelete console is showing correct value second time.
It is showing very strange behavior
React 16.9
I am aware that this class component state:
class JustAnotherCounter extends Component {
state = {
count: 0
};
is the equivalent of using Hooks useState:
function JustAnotherCounter() {
const [count, setCount] = useState(0);
..but what would be the equivalent of the class component state below using Hooks useState?:
class Main extends Component {
state = {
step: 1,
firstName: '',
lastName: '',
jobTitle: '',
jobCompany: '',
jobLocation: '',
}
Any help would be much appreciated.
You can use the same general idea as in class components, just keep in mind that you'll need to spread the object yourself.
const [state, setState] = useState({
step: 1,
firstName: '',
lastName: '',
jobTitle: '',
jobCompany: '',
jobLocation: '',
});
// Setting state like:
setState(prev=>{...prev,firstName:'Joey'});
You can also set up multiple set state calls
const [step,setStep] = useState(1);
const [firstName,setFirstName] = useState('');
const [lastName,setLastName] = useState('');
const [jobTitle,setJobTitle] = useState('');
const [jobCompany,setJobCompany] = useState('');
const [jobLocation,setJobLocation] = useState('');
Another option is to use a reducer, which can be made into a far more complicated example than this:
const [state, dispatch] = useReducer(
(state, action) => ({ ...state, [action.name]: action.value }),
{
step: 1,
firstName: '',
lastName: '',
jobTitle: '',
jobCompany: '',
jobLocation: '',
}
);
// Then update values using:
dispatch({ name: 'firstName', value: 'Joey' });
You can useState to initalize objects like this:
function Main() {
const [form, setValues] = useState({
step: 1,
firstName: '',
lastName: '',
jobTitle: '',
jobCompany: '',
jobLocation: '',
})
}
And then to set the values you can do something like
setValues({
...form,
firstName: 'Andrew',
})
Run
{ npm install react-multi-state }
See how easy it is to use
import { Fragment } from 'react'
function Counter() {
const [state, setState, { setCount }] = useMultiState({
count: 0,
secondCount: 10,
})
return (
<Fragment>
<button onClick={() => setCount(c => c + 1)}>Update count</button>
<button
onClick={() => {
setState(prevState => ({
secondCount: prevState.secondCount + 10,
// use as many `prevState` property values as you wish
}))
}}
>
Update second count
</button>
</Fragment>
)
}
You can easily update the states singly
https://github.com/whizkydee/react-multi-state/
You can save your state value as an object.
Remember when you update your object value using setState, you MUST create a new object instead of updating existing object's value, because setState compare object's reference value, not comparing the object value. This is different from using React Class component.
reference:
https://reactjs.org/docs/hooks-faq.html#should-i-use-one-or-many-state-variables
save your state as an object
const [state, setState] = useState({ left: 0, top: 0, width: 100, height: 100 });
...
// deep clone your object (use other package like lodash or ramda for better performance)
const newObj = JSON.parse(JSON.stringify(state))
// update value and set state
newObj.top = 28
setState(state);
or using spreading for a single line setState
// Spreading "...state" ensures we don't "lose" width and height
setState(state => ({ ...state, left: e.pageX, top: e.pageY }));
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"
}
}
})
You can pass new value like this:
setExampleState({...exampleState, masterField2: {
fieldOne: "a",
fieldTwo: {
fieldTwoOne: "b",
fieldTwoTwo: "c"
}
},
})
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'
}));
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.
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.
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({ occupation: "postman" })
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;
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}
/>
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
}));
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
}))
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>
);
}
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.
useReducer React docs
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.
, 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 }])}
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.
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>
)
})}
If you work with boolean values and arrays This can help you:
const [checkedOrders, setCheckedOrders] = useState<Record<string, TEntity>>({});
const handleToggleCheck = (entity: TEntity) => {
const _checkedOrders = { ...checkedOrders };
const isChecked = entity.id in checkedOrders;
if (isChecked) {
delete _checkedOrders[entity.id];
} else {
_checkedOrders[entity.id] = entity;
}
setCheckedOrders(_checkedOrders);
};
Answers are already there but This type isn't mentioned so take look at the example of this kind ...
const[data,setdata]= useState({
username: [
email,
"required",
//...some additional codes
],
password: [
password,
"required|password-5",
//..additional code if any..
],
})
**To update the state variable email in this example in the input field you can add similar code with your variable name **
<Input
onChangeText={(t) => setdata(prevState=>({...prevState,username:{[0]:t}}))}
value={data.username[0]}
/>
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
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>
)
}
I am learning Redux at school, as such we are using tests to insure we have benchmarks passing to help us in our understanding of the building blocks.
I am up to the portion where I am creating the Reducerfunction and I am almost done \o/ however I can't get one test to pass.
1) returns the initial state by default
And below the console spits back...
Reducer returns the initial state by default:
AssertionError: expected undefined to be an object
at Context. (tests/redux.spec.js:103:49)
My thinking it's because the test handles some of the concerns one would be responsible for e.g importing, creating action types etc. But not all. So maybe I am missing something the test is not providing?
Anyway here is my reducer file:
import pet from "../components/PetPreview";
import { createStore } from "redux";
import { adoptPet, previewPet, addNewDog, addNewCat } from "./action-creators";
// ACTION TYPES
const PREVIEW_PET = "PREVIEW_PET";
const ADOPT_PET = "ADOPT_PET";
const ADD_NEW_DOG = "ADD_NEW_DOG";
const ADD_NEW_CAT = "ADD_NEW_CAT";
// INTITIAL STATE
const initialState = {
dogs: [
{
name: "Taylor",
imgUrl: "src/img/taylor.png"
},
{
name: "Reggie",
imgUrl: "src/img/reggie.png"
},
{
name: "Pandora",
imgUrl: "src/img/pandora.png"
}
],
cats: [
{
name: "Earl",
imgUrl: "src/img/earl.png"
},
{
name: "Winnie",
imgUrl: "src/img/winnie.png"
},
{
name: "Fellini",
imgUrl: "src/img/fellini.png"
}
]
// These dogs and cats are on our intial state,
// but there are a few more things we need!
};
export default function reducer(prevState = initialState, action) {
var newState = Object.assign({}, prevState)
console.log('initialState', typeof initialState)
switch (action.type) {
case PREVIEW_PET:
// console.log('newState', newState)
return Object.assign({}, prevState, {
petToPreview: action.pet
});
break
case ADOPT_PET:
return Object.assign({}, prevState, {
petToAdopt: action.pet
});
break
case ADD_NEW_DOG:
// console.log('action', action.dog)
// console.log('prevState.dogs', prevState.dogs)
newState.dogs = prevState.dogs.concat([action.dog])
return newState;
break
case ADD_NEW_CAT:
// console.log('action', action.dog)
// console.log('prevState.dogs', prevState.dogs)
newState.cats = prevState.cats.concat([action.cat])
return newState;
break;
default:
return prevState;
}
return initialState
}
As you can see after the switch block I am returning the initialState
Shouldn't that be it?
Below is the redux.spec.js file:
import { expect } from "chai";
import { createStore } from "redux";
// You will write these functions
import {
previewPet,
adoptPet,
addNewDog,
addNewCat
} from "../src/store/action-creators";
import reducer from "../src/store/reducer";
const DOGS = [
{
name: "Taylor",
imgUrl: "src/img/taylor.png"
},
{
name: "Reggie",
imgUrl: "src/img/reggie.png"
},
{
name: "Pandora",
imgUrl: "src/img/pandora.png"
}
];
const CATS = [
{
name: "Earl",
imgUrl: "src/img/earl.png"
},
{
name: "Winnie",
imgUrl: "src/img/winnie.png"
},
{
name: "Fellini",
imgUrl: "src/img/fellini.png"
}
];
function getRandomPet(pets) {
return pets[Math.floor(Math.random() * pets.length)];
}
describe("Action creators", () => {
describe("previewPet", () => {
it("returns properly formatted action", () => {
const pet = getRandomPet(DOGS);
expect(previewPet(pet)).to.be.deep.equal({
type: "PREVIEW_PET",
pet: pet
});
});
});
describe("adoptPet", () => {
it("returns properly formatted action", () => {
const pet = getRandomPet(DOGS);
expect(adoptPet(pet)).to.be.deep.equal({
type: "ADOPT_PET",
pet: pet
});
});
});
describe("addNewDog", () => {
it("returns properly formatted action", () => {
const pet = getRandomPet(DOGS);
expect(addNewDog(pet)).to.be.deep.equal({
type: "ADD_NEW_DOG",
dog: pet
});
});
});
describe("addNewCat", () => {
it("returns properly formatted action", () => {
const pet = getRandomPet(CATS);
expect(addNewCat(pet)).to.be.deep.equal({
type: "ADD_NEW_CAT",
cat: pet
});
});
});
}); // end Action creators
describe("Reducer", () => {
let store;
beforeEach("Create the store", () => {
// creates a store (for testing) using your (real) reducer
store = createStore(reducer);
});
it("returns the initial state by default", () => {
// In addition to dogs and cats, we need two more fields
expect(store.getState().petToPreview).to.be.an("object");
expect(store.getState().petToAdopt).to.be.an("object");
});
describe("reduces on PREVIEW_PET action", () => {
it("sets the action's pet as the petToPreview on state (without mutating the previous state)", () => {
const prevState = store.getState();
const pet = getRandomPet(DOGS);
const action = {
type: "PREVIEW_PET",
pet: pet
};
store.dispatch(action);
const newState = store.getState();
// ensures the state is updated properly - deep equality compares the values of two objects' key-value pairs
expect(store.getState().petToPreview).to.be.deep.equal(pet);
// ensures we didn't mutate anything - regular equality compares the location of the object in memory
expect(newState.petToPreview).to.not.be.equal(prevState.petToPreview);
});
});
describe("reduces on ADOPT_PET action", () => {
it("sets the action's pet as the petToAdopt on state (without mutating the previous state)", () => {
const prevState = store.getState();
const pet = getRandomPet(DOGS);
const action = {
type: "ADOPT_PET",
pet: pet
};
store.dispatch(action);
const newState = store.getState();
expect(newState.petToAdopt).to.be.deep.equal(pet);
expect(newState.petToAdopt).to.not.be.equal(prevState.petToAdopt);
});
});
describe("reduces on ADD_NEW_DOG action", () => {
it("adds the new dog to the dogs array (without mutating the previous state)", () => {
const prevState = store.getState();
const pet = getRandomPet(DOGS);
const action = {
type: "ADD_NEW_DOG",
dog: pet
};
store.dispatch(action);
const newState = store.getState();
expect(newState.dogs.length).to.be.equal(prevState.dogs.length + 1);
expect(newState.dogs[newState.dogs.length - 1]).to.be.deep.equal(pet);
expect(newState.dogs).to.not.be.equal(prevState.dogs);
});
});
describe("reduces on ADD_NEW_CAT action", () => {
it("adds the new cat to the cats array (without mutating the previous state)", () => {
const prevState = store.getState();
const pet = getRandomPet(CATS);
const action = {
type: "ADD_NEW_CAT",
cat: pet
};
store.dispatch(action);
const newState = store.getState();
expect(newState.cats.length).to.be.equal(prevState.cats.length + 1);
expect(newState.cats[newState.cats.length - 1]).to.be.deep.equal(pet);
expect(newState.cats).to.not.be.equal(prevState.cats);
});
});
describe("handles unrecognized actions", () => {
it("returns the previous state", () => {
const prevState = store.getState();
const action = {
type: "NOT_A_THING"
};
store.dispatch(action);
const newState = store.getState();
// these should be the same object in memory AND have equivalent key-value pairs
expect(prevState).to.be.an("object");
expect(newState).to.be.an("object");
expect(newState).to.be.equal(prevState);
expect(newState).to.be.deep.equal(prevState);
});
});
}); // end Reducer
Thanks in advance!
in the test cases, one of the test case default says
it("returns the initial state by default", () => {
// In addition to dogs and cats, we need two more fields
expect(store.getState().petToPreview).to.be.an("object");
expect(store.getState().petToAdopt).to.be.an("object");
});
meaning there must be petTpPreview and petToAdapt protery attached to the store at the inital itself. this can be done by adding these two as boject to the state as follows.
// INTITIAL STATE
const initialState = {
petToPreview:{},
petToAdopt: {},
dogs: [
{
name: "Taylor",
imgUrl: "src/img/taylor.png"
},
{
name: "Reggie",
imgUrl: "src/img/reggie.png"
},
{
name: "Pandora",
imgUrl: "src/img/pandora.png"
}
],
cats: [
{
name: "Earl",
imgUrl: "src/img/earl.png"
},
{
name: "Winnie",
imgUrl: "src/img/winnie.png"
},
{
name: "Fellini",
imgUrl: "src/img/fellini.png"
}
]
// These dogs and cats are on our intial state,
// but there are a few more things we need!
};
hope it helps!
All paths in the switch statement lead to a return, which means your return initialState on the penultimate line is unreachable.
Also, your newState is nothing but a clone of prevState, and is unnecessary.
Removing that, and adding a helper function for switchcase combined with some es6 spread love, your code becomes
const switchcase = cases => defaultValue => key =>
(key in cases ? cases[key] : defaultValue);
const reducer = (state = initialState, action) =>
switchcase({
[PREVIEW_PET]: { ...state, petToPreview: action.pet },
[ADOPT_PET]: { ...state, petToAdopt: action.pet },
[ADD_NEW_DOG]: { ...state, dogs: [...state.dogs, action.dog] },
[ADD_NEW_CAT]: { ...state, cats: [...state.cats, action.cat] },
})(state)(action.type);
With all the clutter gone, it's glaringly obvious that the problem is in the fact that your code returns the initialState object if action.type === undefined. And your initialState object contains only dogs and cats properties, whereas your test expects there to be petToPreview and petToAdopt properties.
You can add those properties in the initialState or you can change the test depending on what functionality you want.
Shouldn't you return the previous state by default? The by default is the case that the reducer doesn't care about the action, and simply return its current state, which is prevState in your case.