This content originally appeared on DEV Community and was authored by m0nm
It is common to see useState hook used for state management, However React also have another hook to manage component's state, Which is useReducer hook. In fact, useState is built on useReducer!. So a question arises: What's the difference between the two ? And when should you use either ?
useState hook:
useState hook is a hook used to manipulate and update a functional component. The hook takes one argument which is the initial value of a state and returns a state variable and a function to update it.
const [state, setState] = useState(initialValue)
So a counter app using the useState hook will look like this:
function Counter() {
const initialCount = 0
const [count, setCount] = useState(initialCount);
return (
<>
Count: {count}
<button onClick={() => setCount(initialCount)}>Reset</button>
<button onClick={() => setCount(count - 1)}>Decrement</button>
<button onClick={() => setCount(count + 1)}>Increment</button>
</>
);
}
useReducer hook:
this hook is similar to the useState
hook. However it's able to handle more complex logic regarding the state updates. It takes two arguments: a reducer function and an initial state. The hook then returns the current state of the component and a dispatch function
const [state, dispatch] = useReducer(reducer, initialState)
the dispatch
function is a function that pass an action
to the reducer
function.
The reducer
function generally looks like this:
const reducer = (state, action) => {
switch(action.type) {
case "CASE1":
return "new state";
case "CASE2":
return "new state";
default:
return state
}
}
The action is usually an object that looks like this:
// action object:
{type: "CASE1", payload: data}
The type
property tells the reducer what type of action has happened ( for example: user click on 'Increment' button). The reducer
function then will determine how to update the state
based on the action
.
So a counter app using the useReducer hook will look like this:
const initialCount = 0
const reducer = (state, action) => {
switch (action.type) {
case "increment":
return action.payload;
case "decrement":
return action.payload;
case "reset":
return action.payload;
default:
return state;
}
}
function Counter() {
const [count, dispatch] = useReducer(reducer, initialCount)
return (
<>
Count: {count}
<button onClick={() => dispatch({type: "reset", payload: initialCount}))}>Reset</button>
<button onClick={() => dispatch({type: "decrement", payload: state - 1})}>Decrement</button>
<button onClick={() => dispatch({type: "increment", payload: state + 1})}>Increment</button>
</>
);
}
When should i useReducer() ?
As stated above, The useReducer hook handles more complex logic regarding the state updates. So if you're state is a single boolean
, number
, or string
, Then it's obvious to use useState hook. However if your state is an object (example: person's information) or an array (example: array of products ) useReducer will be more appropriate to use.
Let's take an example of fetching data:
If we have a state that represent the data we fetched from an API, The state will either be one of this three 'states': loading
, data
, or error
When we fetch from an API, Our state will go from loading
( waiting to receive data), to either data
or we'll get an error
Let's compare how we handle state with the useState hook and with the useReducer hook
- With the useState hook:
function Fetcher() {
const [loading, setLoading] = useState(true)
const [data, setData] = useState(null)
const [error, setError] = useState(false)
useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/posts/1').then(res => {
setLoading(false)
setData(res.data)
setError(false)
}).catch((err) => {
setLoading(false)
setData(null)
setError(true)
})
,[])
return (
{loading ? <p>Loading...</p>
: <div>
<h1>{data.title}</h1>
<p>{data.body}</p>
</div> }
{error && <p>"An error occured"</p> }
)
}
- With the useReducer hook:
const initialState = {
loading: true,
data: null,
error: false
}
const reducer = (state, action) => {
switch (action.type) {
case "SUCCESS":
return {
loading: false,
data: action.payload,
error: false
};
case "ERROR":
return {
loading: false,
data: null,
error: true
};
default:
return state;
}
}
function Fetcher() {
const [state, dispatch] = useReducer(reducer, initialState)
useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/posts/1').then(res => {
dispatch({type: "SUCCESS", payload: res.data})
}).catch(err => {
dispatch({type: "ERROR"})
})
} ,[])
return (
{state.loading ? <p>Loading...</p>
: <div>
<h1>{state.data.title}</h1>
<p>{state.data.body}</p>
</div> }
{state.error && <p>"An error occured"</p> }
)
}
As you can see with the useReducer hook we've grouped the three states together and we also updated them together. useReducer hook is extremely useful when you have states that are related to each other, Trying to handle them all with the useState hook may introduce difficulties depending on the complexity and the bussiness logic of it.
Conclusion
To put it simply: if you have a single state either of a boolean
, number
, or string
use the useState hook. And if you're state is an object or an array, Use the useReducer hook. Especially if it contains states related to each other.
I hope this post was helpful, Happy coding!
This content originally appeared on DEV Community and was authored by m0nm

m0nm | Sciencx (2022-02-19T13:17:57+00:00) useState vs useReducer: What are they and when to use them?. Retrieved from https://www.scien.cx/2022/02/19/usestate-vs-usereducer-what-are-they-and-when-to-use-them/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.