What is useReducer Hook?
It is a hook that allows you to manage states in your components. It is similar to useState
but more powerful and flexible since you can manage complex state changes that may involve multiple sub-values.
The useReducer
takes 2 arguments: a reducer function and an initial state.
- The reducer function takes the
current state
and anaction
and then returns thenew state
based on the action type. - The useReducer hook returns an array with 2 values, the
current state
anda dispatch function
. The dispatch function is used to trigger an action and update the state.
import { useReducer } from "react";
const initialState = {
count: 0,
};
function reducer(state, action) {
switch (action.type) {
case "increment":
return { count: state.count + 1 };
case "decrement":
return { count: state.count - 1 };
default:
throw new Error("unknown action");
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({ type: "increment" })}>+</button>
<button onClick={() => dispatch({ type: "decrement" })}>-</button>
</>
);
}
In the example, the useReducer hook is used to manage the state of a counter component.
The state is initialState
that holds the count and the value is 0. The dispatch function contains 2 actions, increment
and decrement
. The count value depends on the dispatch action type.
I think the easier way to think is
- if this is
increment
(type), and the state (result) will be added 1 to the count - if this is
decrement
(type). and the state (result) will be subtracted 1 to the count
Highly recommended checkout switch from MDN docs to get familiar with the statement!
Fetch Data from API
Letβs see how you fetch data with useState
and useReducer
useState
import { useState, useEffect } from "react";
const useFetchApi = () => {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
const url = "https://jsonplaceholder.typicode.com/todos";
try {
const res = await fetch(url);
const data = await res.json();
setData(data);
} catch (error) {
setError(error);
}
setLoading(false);
};
fetchData();
}, []);
return { data, loading, error };
};
export default useFetchApi;
What is a spread operator?
Before we move on to the next section, when you update the state in React, it is recommended to copy the object before modify with the spread operator and keep the original state original!
...state
copy everything fromstate
toupdatedState
count: 5
will update the count to5
from0
greet: "hello"
will update the greet to"Hi"
from"hello"
useReducer
import { useEffect, useReducer } from "react";
const initialState = {
data: [],
loading: true,
error: null,
};
const FETCH_SUCCESS = "fetch_success";
const FETCH_ERROR = "fetch_error";
const reducer = (state, action) => {
switch (action.type) {
case FETCH_SUCCESS:
return {
...state,
data: action.payload,
loading: false,
};
case FETCH_ERROR:
return {
...state,
loading: false,
error: action.payload,
};
default:
throw new Error(`Unknown action: ${action.type}`);
}
};
const useFetchApi = () => {
const [{ data, loading, error }, dispatch] = useReducer(reducer, initialState);
// you could write like this
// const [state, dispatch] = useReducer(reducer, initialState)
useEffect(() => {
const fetchData = async () => {
const url = "https://jsonplaceholder.typicode.com/todos";
try {
const res = await fetch(url);
const data = await res.json();
// if you write state, this needs to be state.data
dispatch({ type: FETCH_SUCCESS, payload: data });
} catch (error) {
// same as above, need to be state.error
dispatch({ type: FETCH_ERROR, payload: error });
}
};
fetchData();
}, []);
// return state
return { data, loading, error };
};
export default useFetchApi;
- If
fetch success (type)
, you shall getpayload: data and loading: false
as a result - If
fetch error (type)
, you shall getpayload: error and loading: false
as a result
Lastly, go to the component file and call the useFetchApi()
.
import useFetchApi from "./hooks/useFetchApi";
function App() {
//I prefer destructuring,
// so I don't have to write "state." over and over! :D
const { data, loading, error } = useFetchApi();
if(loading) return <h2>Loading...</h2>
if(error) return <h2>Whoops, Something has gone wrong...!</h2>
return (
// render the page with lovely data!
);
}
export default App;
Overall, the useReducer hook is a great tool for managing the state of a React application. By using this hook, you can easily update and access to state of the application with minimal code.
I also searched for reasons why use useReducer
instead of useState
for curiosity. I found this article 3 Reasons to useReducer() over useState() and one of the reasons is next state depends on the previous. This makes sense.
The initialState
is an object, so if you update one of the properties, the property value will change. Next time, you update one of the properties, it will always be based on the previous value.
Remember the updater
function from React Hooks - useState blog? If the state depends on the previous value, you would need to write setCount(prevCount => prevCount + 1)
for example, otherwise, the state value may behave strangely.
In the end, both useReducer
and useState
hooks have their pros and cons. Choose the ones that work well or the best for the app!
Resources
- React Docs - useReducer
- MDN docs - switch
- MDN docs - spread syntax
- 3 Reasons to useReducer() over useState()
Thank you!
Thank you for your time and for reading this!