This content originally appeared on DEV Community and was authored by Samson Andrew
So you finally start your React journey or you're just converting your apps to use functional components and you come across the useReducer
hook but can't really wrap your head around it? I'll show you how in few minutes. Read on...
The useReducer
hook is best used when your component has multiple states, to avoid multiple calls to useState
. It is also useful when you want to avoid managing multiple callbacks. I'll show example of each use case.
Example with useState
Consider the following component:
//ES6 and JSX
import React, {useState} from 'react';
function MyBeautifulForm() {
const [username, setUsername] = useState('');
const [password, setpassword] = useState('');
const [email, setEmail] = useState('');
const [age, setAge] = useState(0);
const [height, setHeight] = useState(0);
const [acceptTerms, setAcceptTerms] = useState(false);
function handleFormSubmit(event) {
event.preventDefault();
// processForm();
}
return (
<form onSubmit={handleFormSubmit}>
<input type="text" name="username" value={username} onChange={(e) => setUsername(e.target.value)} />
<input type="password" name="password" value={password} onChange={(e) => setPassword(e.target.value)} />
<input type="email" name="email" value={email} onChange={(e) => setEmail(e.target.value)} />
<input type="number" name="age" value={age} onChange={(e) => setAge(e.target.value)} />
<input type="number" name="height" value={height} onChange={(e) => setHeight(e.target.value)} />
<input type="checkbox" name="terms" checked={acceptTerms ? 'checked' : ''} onChange={(e) => setAcceptTerms(e.target.checked)} />
<input type="submit" name="submit" value="Submit" />
</form>
)
}
That's a whole lot of useState
s to maintain. If our form continues to grow by adding more fields, we would have even more states to maintain and this would someday grow into a big mess. So how do we take care of this?
Your initial thought might be to turn all the initial states to a single state object and pass it to a single useState
call. While this is going to work, we should remember that calling setState
in function components
replaces the state rather than merging as in class components
. States can be lost or overridden unintentionally if updated this way most especially if we're coming from class components.
This is where the useReducer
hook shines. Let's see how we can use it to simplify the previous code:
Example with useReducer
//ES6 and JSX
import React, {/*useState,*/ useReducer} from 'react';
function reducer(state, action) {
switch(action.type) {
case 'USERNAME':
return {...state, username: action.payload};
case 'PASSWORD':
return {...state, password: action.payload};
...
...
default:
return state;
}
}
function MyBeautifulForm() {
const initialState = {
username: '',
password: '',
email: '',
age: 0,
height: 0,
acceptTerms: false
}
// const [formState, setFormState] = useState(initialState); // we will come bct to this later
const [state, dispatch] = useReducer(reducer, initialState);
function handleFormSubmit(event) {
event.preventDefault();
// processForm();
}
return (
<form onSubmit={handleFormSubmit}>
<input type="text" name="username" value={state.username} onChange={(e) => dispatch({type: 'USERNAME', payload: e.target.value})} />
<input type="password" name="password" value={state.password} onChange={(e) => dispatch({type: 'PASSWORD', payload: e.target.value})} />
<input type="email" name="email" value={state.email} onChange={(e) => dispatch({type: 'EMAIL', payload: e.target.value})} />
<input type="number" name="age" value={state.age} onChange={(e) => dispatch({type: 'AGE', payload: e.target.value})} />
<input type="number" name="height" value={state.height} onChange={(e) => dispatch({type: 'HEIGHT', payload: e.target.value})} />
<input type="checkbox" name="terms" checked={state.acceptTerms ? 'checked' : ''} onChange={(e) => dispatch({type: 'TERMS', payload: e.target.checked})} />
<input type="submit" name="submit" value="Submit" />
</form>
)
}
And now, we have a single state object (initialState) and a single state updater function (reducer).
Let's discuss what just happened:
useState
You may have been wondering what we mean when we write:
const [something, setSomething] = useState(somethingElse);
The above line of code used something known as javascript array destructuring
. The two constants (something and setSomething) is set to the first two values of the array returned by the call to useState.
Consider the following codes:
// const [fruit, setFruit] = useState('Apple');
let result = useState('Apple');//apple is the initial state
// you can pass null to the function
// if your component does not have an initial state
// console.log(result) // ['Apple', functionToSetAnotherName];
// later
let state = result[0];
let updater = result[1];
// let fruit = result[0];
// let setFruit = result[1];
//Well, this is not worth it...
//array destructuring is short and quite simple to use.
You may declare a state as follows:
const [car, name] = useState('Toyota');
if you want to create unnecessary headache for yourself and other team members.
Using [something, setSomething]
is the adopted way of doing this, so you have to follow the pattern.
Whenever your state changes and you want to update it, the only way to do that is by using the second item returned by the setState
function. I call it the state updater
. This way, you are sure that your state will always maintain the correct value. Always use the function to update your state and avoid changing the value directly (mutation).
useReducer
The process of setting or updating a state with the useReducer may not be as straightforward as with useState but it is more elegant. The steps are as follows:
- Declare your
intialState
object - Declare your reducer function
- Declare your state with the
const [state, dispatch] = useReducer(reducer, initialState)
syntax - Dispatch your actions to the reducer function with the
dispatch
function (this would have been thestate updater
function inuseState
, but since we do not keep a separate state for each component, we have to send the update request to the reducer) - Update your state within the reducer function using the type and the payload information provided by the dispatcher.
- The return value after the update is the current state which ensures that our state is up to date
What you gain from this is simplicity as you do not need to pass different callbacks around - you only pass a single dispatch
which returns only one value (currentState)
Finally
Let's demonstrate how to use useState
with a single state object.
Avoid multiple calls to useState
//ES6 and JSX
import React, {useState} from 'react';
function MyBeautifulForm() {
const initialState = {
username: '',
password: '',
email: '',
age: 0,
height: 0,
acceptTerms: false
}
const [formState, setFormState] = useState(initialState);
function updateState(state) {
setFormState(formState => {...formState, ...state});
}
function handleFormSubmit(event) {
event.preventDefault();
// processForm();
}
return (
<form onSubmit={handleFormSubmit}>
<input type="text" name="username" value={state.username} onChange={(e) => updateState({username: e.target.value})} />
<input type="password" name="password" value={state.password} onChange={(e) => updateState({password: e.target.value})} />
<input type="email" name="email" value={state.email} onChange={(e) => updateState({email: e.target.value})} />
<input type="number" name="age" value={state.age} onChange={(e) => updateState({age: e.target.value})} />
<input type="number" name="height" value={state.height} onChange={(e) => updateState({height: e.target.value})} />
<input type="checkbox" name="terms" checked={state.acceptTerms ? 'checked' : ''} onChange={(e) => updateState({terms: e.target.checked})} />
<input type="submit" name="submit" value="Submit" />
</form>
)
}
In a simple way, the code above can be used to achieve the same result as the previous example with useReducer
. Although, for some use cases, you might find one to be more elegant than the other. The aim of this article is to explain the basic usage of the useReducer
hook and not to promote one at the expense of the other.
Conclusion
We have seen how the useReducer
hook can be used to combine multiple states into a single state object and updated with the reducer function using the information provided by the dispatch function. I will answer any question at the comment section.
Thanks ;)
If you're still reading, you can check out my clone of dev.to with React.js at Dev to Blog. The source code is available at the public repo Github Link
This content originally appeared on DEV Community and was authored by Samson Andrew
Samson Andrew | Sciencx (2021-07-24T13:59:42+00:00) React useReducer for dummies. Retrieved from https://www.scien.cx/2021/07/24/react-usereducer-for-dummies/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.