useReducer is a built-in React hook.
In this article, you will learn what the useReducer hook is, what it is used for, when to choose the useReducer hook, and when to choose the useState hook. To make it easier to understand than boring long sentences, we will also walk through 2 complete examples (the first one is simple, and the second one is a little bit more complex) of using the useReducer hook in action.
Overview
The useReducer hook allows you to manage the state in a functional component, and it also provides a function that updates the state and re-renders the component. It is very similar to the useState hook but has a few advantages in certain cases, which we will discuss later in the Choosing between useReducer and useState section.
const [state, dispatch] = useReducer(reducer, initialState);
Where:
- state: the state returned by useReducer.
- dispatch: a function used to update the state.
- reducer: a function that takes 2 arguments: the first one is the previous state and the second one is an action which is an object containing information used for updating the state (This explanation can be a little confusing. Don’t worry, the examples given in this article will show it’s not that complicated).
- initialState: the state at the beginning.
Choosing between useReducer and useState
In simple words:
- useReducer is better when you have kind of connected and complex states. You can write some logic that basically runs whenever you want to change the state to do more complex updates than just setting a new value.
- useState is better when your states are NOT complex and interdependent. In these cases, using useState makes your code much shorter and easier to understand than using useReducer.
Simple Example: useReducer + form input
The purpose of this example is to help you understand the basics of the useReducer hook and see that, many times, using useState is the better solution.
Preview
This example app contains a form with a text input and a button. The text input lets the user enter his / her name. The button is disabled by default and only becomes enabled when the entered name is valid (We assume that a name is valid when its length is greater than or equal to 3). When the button is clicked, the thing in the text input will be clear.
The code with useReducer
// App.js
import React, { useReducer } from 'react';
const App = () => {
// The initial state
const initialState = {
name: '',
isValid: false,
};
// The reducer function
const [nameState, dispatch] = useReducer((state, action) => {
switch (action.type) {
case 'CHANGE':
// Update the state
return {
name: action.value,
isValid: action.value.length >= 3 ? true : false,
};
case 'SUBMIT':
// Reset the state
return {
...initialState,
};
default:
return state;
}
}, initialState);
// This function is called when the user types something
const changeHandler = (event) => {
dispatch({
type: 'CHANGE',
value: event.target.value,
});
};
// This function is trigger when the user clicks the "submit" button
const submitHandler = (event) => {
event.preventDefault();
alert(nameState.name);
dispatch({
type: 'SUBMIT'
})
};
return (
<form style={styles.container}>
<input
type="text"
value={nameState.name}
onChange={changeHandler}
style={styles.input}
/>
<button disabled={!nameState.isValid} onClick={submitHandler}>
Submit
</button>
</form>
);
};
export default App;
const styles = {
container: {
padding: 50,
},
};
The code with useState (more concise)
// App.js
import React, { useState } from 'react';
const App = () => {
const [name, setName] = useState('');
const [isValid, setIsValid] = useState(false);
// This function is triggered when the user types something
const changeHandler = (event) => {
const enteredName = event.target.value;
setName(enteredName);
if (enteredName.length >= 3) {
setIsValid(true);
} else {
setIsValid(false);
}
};
// This function is trigger when the user clicks the "submit" button
const submitHandler = (event) => {
event.preventDefault();
alert(name);
setName('');
setIsValid(false)
};
return (
<form style={styles.container}>
<input
type="text"
value={name}
onChange={changeHandler}
style={styles.input}
/>
<button disabled={!isValid} onClick={submitHandler}>
Submit
</button>
</form>
);
};
export default App;
const styles = {
container: {
padding: 50,
},
};
Because the state, in this case, is simple, it seems that using useReducer will make the code more verbose.
Complex Example: When useReducer Shines
In this example, we are going to create a fried chicken ordering application. Users can buy 1 or more fried chicken sets with options including Coke and fried potatoes.
Note that the “-” buttons will be disabled when the item corresponding to it has a zero count
Preview
The complete code
// App.js
import React, { useReducer } from 'react';
const App = () => {
const initialOrderState = {
chicken: 0,
coke: 0,
fries: 0,
total: 0,
};
const [orderState, dispatch] = useReducer((state, action) => {
switch (action.type) {
case 'CHICKEN':
return {
...state,
chicken: state.chicken + action.value,
total: state.total + action.value * 3, // a chicken piece costs 3$
};
case 'COKE':
return {
...state,
coke: state.coke + action.value,
total: state.total + action.value * 0.5, // a coke costs 0.5$
};
case 'FRIES':
return {
...state,
fries: state.fries + action.value,
total: state.total + action.value, // a fries set costs 1$
};
case 'RESET':
return initialOrderState;
default:
return state;
}
}, initialOrderState);
const buttonHander = (type, value) => {
dispatch({
type: type,
value: value,
});
};
// This function is associated with the reset button
const resetOrder = () => {
dispatch({ type: 'RESET' });
};
// This function is associated with the order button
const submitOrder = () => {
alert(`You have submitted your order successfully - ${orderState.total}`);
};
return (
<div style={styles.container}>
<div>
<p>Pricing: 3$ / chicken piece, 0.5$ / 1 Coke, 1$ / fries </p>
</div>
<hr />
<div>
<p>
Fried chicken: {orderState.chicken}
<button onClick={() => buttonHander('CHICKEN', 1)}>+</button>
<button
onClick={() => buttonHander('CHICKEN', -1)}
disabled={orderState.chicken === 0}
>
-
</button>
</p>
<p>
Coke: {orderState.coke}
<button onClick={() => buttonHander('COKE', 1)}>+</button>
<button
onClick={() => buttonHander('COKE', -1)}
disabled={orderState.coke === 0}
>
-
</button>
</p>
<p>
Fired potats: {orderState.fries}
<button onClick={() => buttonHander('FRIES', 1)}>+</button>
<button
onClick={() => buttonHander('FRIES', -1)}
disabled={orderState.fries === 0}
>
-
</button>
</p>
<br />
<p>Total: {orderState.total}$</p>
<p>
<button onClick={resetOrder}>Reset</button>
<button onClick={submitOrder}>Order</button>
</p>
</div>
</div>
);
};
export default App;
const styles = {
container: {
padding: 50,
},
};
Conclusion
Congratulation! At this point, you should have a better understanding of the useReducer hook in React. You’ve learned about the syntax and when you should use it instead of the useState hook. The examples given in this article should also give you a better feeling of using it in practice.
Keep the ball rolling and continue exploring more new and exciting things about modern React development by taking a look at the following articles:
- React & TypeScript: Using useRef hook example
- 2 ways to display Math Symbols in React
- Best open-source Admin Dashboard libraries for React
- 5 Best Open-Source HTTP Request Libraries for React
- React + MUI: Create Dark/Light Theme Toggle
- Most popular React Component UI Libraries
You can also check our React category page and React Native category page for the latest tutorials and examples.