Is There a Generic Way to Set State in React Hooks? How to Manage Multiple States

Is there a generic way to set state in React Hooks? How to manage multiple states?

Yes, with hooks you can manage complex state (without 3rd party library) in three ways, where the main reasoning is managing state ids and their corresponding elements.

  1. Manage a single object with multiple states (notice that an array is an object).
  2. Use useReducer if (1) is too complex.
  3. Use multiple useState for every key-value pair (consider the readability and maintenance of it).

Check out this:

// Ids-values pairs.
const complexStateInitial = {
input1: "",
input2: "",
input3: ""
// .. more states
};

function reducer(state, action) {
return { ...state, [action.type]: action.value };
}

export default function App() {
const [fromUseState, setState] = useState(complexStateInitial);

// handle generic state from useState
const onChangeUseState = (e) => {
const { name, value } = e.target;
setState((prevState) => ({ ...prevState, [name]: value }));
};

const [fromReducer, dispatch] = useReducer(reducer, complexStateInitial);

// handle generic state from useReducer
const onChangeUseReducer = (e) => {
const { name, value } = e.target;
dispatch({ type: name, value });
};

return (
<>
<h3>useState</h3>
<div>
{Object.entries(fromUseState).map(([key, value]) => (
<input
key={key}
name={key}
value={value}
onChange={onChangeUseState}
/>
))}
<pre>{JSON.stringify(fromUseState, null, 2)}</pre>
</div>

<h3>useReducer</h3>
<div>
{Object.entries(fromReducer).map(([key, value]) => (
<input
name={key}
key={key}
value={value}
onChange={onChangeUseReducer}
/>
))}
<pre>{JSON.stringify(fromReducer, null, 2)}</pre>
</div>
</>
);
}

Edit Handle Multiple State

Notes

  • 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:
setState(prevState => {
// Object.assign would also work
return {...prevState, ...updatedValues};
});

Refer to React Docs.

Set multiple state values with React useState hook

There is nothing wrong with your solution. The name you give to those states is only local to your component when you destructure the array that useState returns. React does not know about the name you give to those elements in the array that useState returns. Don't worry about the generic name that shows up in dev-tools, just try your solution and you'll see it works.

For something that shows up in dev-tools, you could use useDebugValue.

Managing multiple states inside hook

In terms of updating state, first template is much easy and simple, because each stateUpdate function will be responsible for updating single value, that can be obj/array/int/bool/string. Another thing is, to access each value of the state (it will be an object), you need to write states.slides, states.tagName etc.

With first case, state update will be simply:

// only value in case of int/string/bool
updateState({ [key]: value }) or updateState(value);

But in second case, state will be an object with multiple keys, so to update any single value you need to copy the object, then pass the updated key-value. Like this:

updateState({
...obj,
[key]: newValue
})

To update the slides array:

updateState(prevState => ({
...prevState,
slides: newArray
}))

Handling complex state update:

Use useReducer to handle the complex state update, write a separate reducer function with action types and for each action type so the calculation and return the new state.

For Example:

const initialState = { slides: [], tagName: [], currentSlide: 0 };

function reducer(state, action) {
switch (action.type) {
case 'SLIDES':
return { ... };
case 'TAGNAME':
return { ... };
case 'CURRENT_SLIDE':
return { ... }
default:
throw new Error();
}
}

function Counter({initialState}) {
const [state, dispatch] = useReducer(reducer, initialState);
....
}

React change state from another component without passing setState method

It might help to think in terms of controlled vs uncontrolled components. You may be familiar with this from core elements like <input>s, where you can either pass in a defaultValue prop, and let the input handle everything ("uncontrolled"), or you can pass in value and onChange and handle things yourself ("controlled"). You can design your table component as either a controlled component or uncontrolled component too.

Doing it as an uncontrolled component, you might pass in a prop that sets the initial sorting, but afterwards everything is handled by the table. The parent won't get notified and won't update its state:

const Parent = () => {
const [items, setItems] = useState(/* some array */);

return <MyTable items={items} defaultSort="asc" />
}

const MyTable = ({ items, defaultSort }) => {
const [sort, setSort] = useState(defaultSort ?? 'asc');
const sortedItems = useMemo(() => {
if (sort === 'asc') {
return [...items].sort(/* insert sort function here */)
} else {
return [...items].sort(/* insert sort function here */)
}
}, [items, sort]);

return (
<>
<button onClick={() => setSort(sort === 'asc' ? 'dsc' : 'asc')}>
Change Sort
</button>
{sortedItems.map(() => /* etc */)}
</>
)
}

If instead you do a controlled component, then the parent is in charge of the state, and the child just notifies the parent of relevant changes

const Parent = () => {
const [items, setItems] = useState(/* some array */);
const [sort, setSort] = useState('asc');
const sortedItems = useMemo(() => {
if (sort === 'asc') {
return [...items].sort(/* insert sort function here */)
} else {
return [...items].sort(/* insert sort function here */)
}
}, [items, sort]);

return <MyTable items={sortedItems} onSortToggled={() => setSort(sort === 'asc' ? 'dsc' : 'asc')} />
}

const MyTable = ({ items, onSortToggled}) => {
return (
<>
<button onClick={onSortToggled}>
Change Sort
</button>
{items.map(() => /* etc */)}
</>
)
}

If you add in some extra code to check for undefineds, it's possible to make your table support both controlled and uncontrolled modes, based on which set of props it is passed. But it should just be one or the other; you shouldn't try to have both components be managing state simultaneously, as this just adds opportunities for the states to get out of sync and bugs to be introduced.

the state that stores the items must be changed which is outside of my table component

If this is one of your requirements, then you're basically doing the controlled component version, and thus you must accept a function from the parent component which describes how to do so. The parent component is the only one who knows what state they have and how to update it.

how to update multiple state at once using react hook react.js

You can use one useState with object value for updating the styles at once:

import React, { useEffect, useState } from 'react';

export default function (props) {
const [style, setStyle] = useState({ color: 0, size: 0, weight: 0 });

const onClickRandomButton = () => {
setStyle({
color: Math.random() * 10,
size: Math.random() * 10,
weight: Math.random() * 10,
});
};

return (
<div>
<button onClick={onClickRandomButton}>random</button>
</div>
);
}

And if in any method you want to update just one property, for example: color, you could do something like this:

...
const handleEditColor = color => {
setStyle({
...style,
color
});
};
...

How to update nested state properties in React

In order to setState for a nested object you can follow the below approach as I think setState doesn't handle nested updates.

var someProperty = {...this.state.someProperty}
someProperty.flag = true;
this.setState({someProperty})

The idea is to create a dummy object perform operations on it and then replace the component's state with the updated object

Now, the spread operator creates only one level nested copy of the object. If your state is highly nested like:

this.state = {
someProperty: {
someOtherProperty: {
anotherProperty: {
flag: true
}
..
}
...
}
...
}

You could setState using spread operator at each level like

this.setState(prevState => ({
...prevState,
someProperty: {
...prevState.someProperty,
someOtherProperty: {
...prevState.someProperty.someOtherProperty,
anotherProperty: {
...prevState.someProperty.someOtherProperty.anotherProperty,
flag: false
}
}
}
}))

However the above syntax get every ugly as the state becomes more and more nested and hence I recommend you to use immutability-helper package to update the state.

See this answer on how to update state with immutability-helper.

Use same useState in different components

You should use contextApi to handle this. I have shared a sample code which helps you understand more about context api.

Context api helps you share the states and functions of a component
with other components inside the particular project.

In Filecontext.jsx you can see createContext which helps you in creating a context.

In App.jsx, we have created the states and functions which has to be shared among the components and wrapped the components which can access the datas with that context by importing it.

In Formcomponent.jsx, I am using useContext to use the states and functions created in the App.jsx.

Filecontext.jsx

import { createContext } from 'react'
export const Filecontext = createContext({});

App.jsx

import { Filecontext } from './Contexts/Filecontext';
import { useState } from 'react'

function App() {
const [name, setName] = useState("")
const [email, setEmail] = useState("")
const [mobileno, setMobileno] = useState("")
const showAlert = () => {
alert(`Hello ${name}`);
}

return (
<div className="App">
<Filecontext.Provider value={{ name, setName, email, setEmail, mobileno, setMobileno, showAlert }}>
<Formcomponent />
<Listcomponent />
</Filecontext.Provider>
</div>
);
}

export default App;

Formcomponent.jsx

import { Filecontext } from '../Contexts/Filecontext';
import { useContext } from 'react'

export default function Formcomponent() {
const { setName, setEmail, setMobileno, showAlert } = useContext(Filecontext)

return (
<>
<div className="form-group">
<label>Name : </label>
<input type="text" onChange={(e) => { setName(e.target.value) }} />
</div>
<div className="form-group">
<label>Email : </label>
<input type="email" onChange={(e) => { setEmail(e.target.value) }} />
</div>
<div className="form-group">
<label>Mobile No : </label>
<input type="number" onChange={(e) => { setMobileno(e.target.value) }} />
</div>
<div className="form-group">
<input type="submit" value="submit" onClick={() => { showAlert() }} />
</div>
</>
)
}

Two way binding more than one input in React w/ Hooks

You might consider store both user and message in one {data} state holder, and at handleChange modified just the relevant key in state object


  const [data, setData] = useState({user: "", message: ""})

const handleChange = e => setData({...data, [e.target.name]: e.target.value})

<input name="message" value={data.message} onChange={e => handleChange(e)} />
<input name="user" value={data.user} onChange={e => handleChange(e)} />



Related Topics



Leave a reply



Submit