React Hooks - useReducer Tutorial (With Example)
React Hooks - useReducer Tutorial (With Example)

Hello and welcome back to the channel. In this video you will learn useReducer hook. This is the alternative approach to store data inside component that useState. As you can understand the idea came from Redux approach with it's reducers.
Let's jump right into it.

Here I have empty create-react-app project where we can directly start writing code. If you don't know how to generate react project with create-react-app I made a video on that so go check it out first.

I you can see in browser I have just a single h1 tag and it's our App.js component. Let's have a look. So it's a normal stateless React component with markup inside.

As we are using React bigger than 16 version (actually 17) we can use hooks inside without installing any additional packages.

So why do we need useReducer and how to use it? So actually it's an alternative to useState. If you didn't see my video on useState go check it first. Also if you are familiar with Redux you already know the concept.

Let's check on the example. For example we want a component that will render article. So normally we will fetch an article and store in state, but additionally we need to store isLoading property so we can highlight for user that something is loading and error property where we will store our error that we want to show if request fails.

This means that we need to create 3 states for this or pack all them in single object. When we have complex state or a lot of business logic regarding how our state changes it's a good idea to use useReducer how.

import React, { useReducer } from "react";

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

So here we used useReducer hook from React. As you can see we need to pass 2 arguments inside reducer and initialState. As a result we are getting changed state and dispatch method.

At this point I should explain a bit Redux approach so it will be more clear what useReducer is doing. So in Redux when component triggers some changes like "User clicks on button" or "Register request started" we want to dispatch an action. Action has a name inside and optionally some data. Also we have a state and this state reacts to our actions and changes itself. In this case we have single flow of data. Our component doesn't listen directly to actions but just triggers them. It listens only for data from state.

Exactly this approach useReducer implements. So we need first to create initialState. It's a state that we want to change. As we discussed previously we have there 3 properties: isLoading, error, article.

const initialState = {
  isLoading: false,
  error: null,
  article: null,
};

now we need to define a reducer. This is a just a function what will change the state by actions that we will trigger. For now let's first create it and simply return state.

const reducer = (state, action) => {
  return state
}

Now what we are getting back from useReducer? It's a state and a dispatch function. So state is our updated state which will always up to date and dispatch is a way how we can trigger actions from our component. Let's console log state first.

console.log("rerender", state);

As you can see in browser this is our initialState. Now we can try to change our state. Let's make this application as real as possible. Normally fetching of article is asynchronous operation and we need 3 actions: start, success and failure. So let's for now dispatch getArticleStart when we click on the button.

<button onClick={() => dispatch({ type: "getArticleStart" })}>
  Start getting article
</button>

So we just call dispatch function on click and pass an object with type property. Type is mandatory when we are dispatching actions. So we just said that on click we want to get the article. Now our reducer need to change the state accordingly when we dispatching getArticleStart action.

const reducer = (state, action) => {
  switch (action.type) {
    case "getArticleStart":
      return {
        ...state,
        isLoading: true,
      };
    default:
      return state;
  }
};

So here we wrote a switch on action.type. So inside we can console.log action and see that we are getting our every action. Now we want to set isLoading property to true when getArticleStart happens.

As you can see in browser our state is updated with isLoading = true.

Now I want to make this example much more realistic but for this we need to use useEffect hooks. I made already a video on it so if you didn't watch it put this video on pause and watch it first. It will link it somewhere here.

What I want to do is to make a real API request and fully react on it with our useReducer. First of all we need to install json-server for this. It's a npm package which help us to create a fake API really fast.

Let's install json-server globally.

npm install -g json-server

Now we can define db.json file which will be our database.

{
  "posts": [
    {
      "id": "1",
      "title": "Use state hooks"
    },
    {
      "id": "2",
      "title": "Use effect hooks"
    }
  ]
}

Now we just need to start our json-server

json-server --watch db.json --port 3004

Now we can access a specific article that we want to fetch by

http://localhost:3004/posts/1

The next step to install an axios library because we need a third party package to fetch data from API.

npm install axios

And let's create an effect where we want to make our request.

import axios from "axios";


useEffect(() => {
  axios.get("http://localhost:3004/posts/1").then((res) => {
    console.log("res", res);
  });
}, []);

Just to remind you we are making all side effects like API call inside useEffect and we are providing empty array of dependencies as a second argument to say that we don't have any dependencies and it will be called only once after mounting the component.

As you can see in browser, we are getting a valid response from our fake API. Now we want to call getArticleStart at the beginning of our fetch, getArticleSuccess when it is finished and getArticleFailure if it failed.

useEffect(() => {
  dispatch({ type: "getArticleStart" });
  axios
    .get("http://localhost:3004/posts/1")
    .then((res) => {
      dispatch({ type: "getArticleSuccess", payload: res.data });
    })
    .catch(() => {
      dispatch({ type: "getArticleFailure" });
    });
}, []);

As you can see the only new thing here is that we provided in success not only type but also a payload. We can actually provide inside any properties that we want but standard naming is payload.

Now we need to react to this dispatches inside our reducer.

const reducer = (state, action) => {
  switch (action.type) {
    case "getArticleStart":
      return {
        ...state,
        isLoading: true,
      };
    case "getArticleSuccess":
      return {
        ...state,
        isLoading: false,
        article: action.payload,
      };
    case "getArticleFailure":
      return {
        ...state,
        isLoading: false,
      };
    default:
      return state;
  }
};

As you can see in browser our state from useReducer is updated after every action and it completely valid. Now we can use properties from state in template like we want.

return (
  <div>
    <h1>React hooks for beginners</h1>
    {state.isLoading && <div>Loading...</div>}
    {state.error && <div>Something is broken</div>}
    {state.article && <div>{state.article.title}</div>}
  </div>
);

As you can see in browser, everything is working as intended.

So when to use useState and when useReducer. It's a matter of personal preference but I recommend thinking about useReducer if you have a complex logic or lot's of properties like 10 and above. It also really helps same as Redux to move logic completely out of component and just call plain actions.

If "React hooks for beginners" is too easy for you I have a full hooks course which is going 8 hours where we are creating real application from scratch. I will link it down in the description below.

📚 Source code of what we've done