Synchronizing nested array within an object in useState hook - javascript

I have defined a useState object as follow:
const [groupDetails, setGroupDetails] = React.useState([
{ fullName: "", phoneNo: "", gender: "" },
]);
const [state, setState] = React.useState({
fullName: "",
phoneNo: " ",
email: "",
gender: "",
idProof: "",
noOfPeople: "",
bookingId: "",
detailsOfPeople: groupDetails,
});
I have done populating the nested array groupDetails, but when I do:
console.log("state object:",state);
The field "detailsOfPeople" that holds that array groupDetails shows nothing. But if I print "groupDetails" all values are shown.
Within useEffect function I'm doing this:
useEffect(() => {
setGroupDetails(groupDetails);
setState(state);
}, [state, groupDetails]);

React State hooks are working async so you shouldn't wait state change after the setState call. You can catch the end of state change via useEffect.
useEffect(() => {
setGroupDetails(groupDetails);
setState(prevState => ({...prevState, detailsOfPeople: groupDetails}) );
}, [groupDetails]);

Related

How to store action.payload in redux-toolkit

I want to set state.name to "Alex" and age plus one after click the Login function, and set email to action.payload.
Here is my user state slice
const userSlice = createSlice({
name: "user",
initialState: { value: { name: "", age: 0, email: "" } },
reducers: {
login: (state, action) => {
state.value.name = "Alex"
state.value.age = state.value.age + 1
state.value.email = action.payload
},
}
})
Here is my useDispatch
function Login() {
const dispatch = useDispatch()
return (
<div>
<button
onClick={() => {
dispatch(login({ email: 'example#gmail.com' }))
}}
>
Login
</button>
</div>
)
}
There is an error: Objects are not valid as a React child (found: object with keys {email}). If you meant to render a collection of children, use an array instead.
I am using Redux Toolkit.
It seems you are likely selecting this state.user.value state in a component and rendering the constituent parts, i.e. value.name, value.age, and value.email. This is fine until you update the state.user.value.email to have the payload value of { email: 'example#gmail.com' } when dispatching the login action. This makes the value.email an object and unrenderable.
Either dispatch the login action with the exact payload value you want to save into state:
<button
onClick={() => {
dispatch(login('example#gmail.com'));
}}
>
Login
</button>
or unpack the email property from the payload correctly in the case reducer:
const userSlice = createSlice({
name: "user",
initialState: {
value: {
name: "",
age: 0,
email: ""
}
},
reducers: {
login: (state, action) => {
state.value.name = "Alex";
state.value.age = state.value.age + 1;
state.value.email = action.payload.email;
},
}
});
I would suggest also simplifying the state to unnest the actual values you want to store.
Example:
const userSlice = createSlice({
name: "user",
initialState: {
name: "",
age: 0,
email: ""
},
reducers: {
login: (state, action) => {
state.name = "Alex";
state.age = state.age + 1;
state.email = action.payload; // { payload: 'example#gmail.com' }
},
}
});
...
const user = useSelector(state => state.user);
...
<>
Name: {user.name}
Age: {user.age}
Email: {user.email}
</>

How to change a "sub value" from an initial value?

I'm using Next.js with typescript.
I have a form with multiple inputs. I didn't want to make a useState for each input for obvious reasons and I didn't want to use a form-ready library either. I just wanted to find the solution to this.
How can I get the handleInputChange function to change the value of the director's name, for example, in the "initial state"?
const formInitialValues = {
teamName: "",
bornAt: "",
logo: "",
director: {
name: "",
email: "",
phone: "",
},
};
const [formData, setFormData] = useState(formInitialValues);
const handleInputChange = (e) => {
const { name, value } = e.target;
setFormData({
...formData,
[name]: value,
});
};
...
<input
type="text"
placeholder="Director name"
name="directorName"
id="directorName"
required
value={formData.director.name}
onChange={handleInputChange}
/>
...
You can do it like below:
const handleInputChange = (e) => {
const { name, value } = e.target;
setFormData(() => {
...formData,
director: {
...formData.director,
[name]: value
}
});
};
or using callback of setter method:
setFormData((prevFormData) => ({
...prevFormData,
director: {
...prevFormData.director,
[name]: value
}
}));

How to update deep object state in React?

I wrote code like below. And call updatePartlyState(), but state now updated.
How to update deep object, detail.age and detail.country to initialState?
const initialState = {
id: '',
name: '',
detail: {
age: '',
country: '',
},
}
const [state, setState] = useState(initialState);
// not work
const updatePartlyState = () => {
setState({
...state,
detail: initialState.detail,
})
}
setState can be passed with a function to functionally update the state based on the previous state. Just spread it and reapply the necessary keys.
const updatePartlyState = () => {
setState(prevState => ({
...prevState,
detail: initialState.detail,
}))
}
Or, you can use libraries like immer if it's becoming more often.
this.setState(prevState => ({
...prevState,
someProperty: {
...prevState.someProperty,
someOtherProperty: {
...prevState.someProperty.someOtherProperty,
anotherProperty: {
...prevState.someProperty.someOtherProperty.anotherProperty,
flag: false
}
}
}
}))```

how to write a multi lines state with Hooks useState in ReactJs

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 }));

React Hooks useState() with Object

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>
)
}

Categories