From React Class component to Functional component with hooks

Discover the React useReducer hook

In a previous episode, we have created a React Simple Counter with the hook useState. But, you should prefer using the useReducer hook instead of the useState hooks when facing the two following situations :

  • there is local complex logic containing many sub-values,
  • the next state depends on the previous state.

Let’s see what is the useReducer hook and what is its structure in this article.

Summary

Import the React useReducer hook

The useReducer hook is imported the same we did to import the useState hook.

Import the React useReducer hook in a create-react-app

import{ useReducer } from ‘react’;

If you need to import many hooks in the same file…

import{ useEffect, useReducer } from ‘react’;

Import the React useReducer hook in a pen

const { useReducer} = React;

If you need to import many hooks in the same pen…

const { useEffect, useReducer } = React;

Call the React useReducer hook in a component

Review how to call the useState hook:

const [state, updateState] = useState(0);

How to call the React useReducer hook in functional component

The useReducer hook is called inside a component the same way we call the useState hook : in a constant, at the top of the function.

At the first view the structure seems similar but, obviously, there are some differences:

  • the dispatch method replace the updating function updateState;
  • the useReducer has three arguments:
    • a reducer which type is (state, action) => newState and return the current Local State and a dispatch method which makes us think of Redux;
    • an initial value initialArg;
    • the init function.
const [state, dispatch] = useReducer(reducer, initialArg, init);

Initial state and Current state when using the useReducer hook

There are two way to initialize the state when using the useReducer hook: the simple way and the lazy way..

The simple way to initialize the useReducer state

The simplest way to initialize the useReducer state is to pass it as the second argument.

It is at this moment that the state property name is given. In this example the given name will be “count”.

Note that when initializing the useReducer state this way, there is not the init function!

The simple way to initialize the useReducer state with a value

The state property name and the value are directly passed in the useReducer hook.

function MyComponent() {
    const [state, dispatch] = useReducer(reducer, {count: 0});
    return (
      // code with dispatch calling…
    )
}

Remark: When using a reset button, we should write { count: 0 } in the reset “function” too… Place the initial state in a constant outside the component function could be a good idea!

The simple way to initialize the useReducer state with a constant containing the value

  1. A constant named initialState can be declared outside the component function.
  2. The state property name and the initial value of this state become the constant value.
  3. The constant is called as the second argument passed to the useReducer hook.
const initialState = {count: 0};
function MyComponent() {
    const [state, disptach] = useReducer(reducer, initialState);
    return (
      // code with dispatch calling…
    )
}

The lazy way to initialize the useReducer state

This second solution to initialize the useReducer state use a init function as third argument. The initial state will equal init(initialArg).

  1. The initialArg is passed as argument to the component function.
  2. Then, the initialArg is passed as second argument to the useReducer at the top of the function.
  3. The init function is passed as third argument to the useReducer.
  4. The init function is declared outside the component function. This way it is possible to extract the logic to calculate the initial local state outside the reducer. So, it is easier to reset the local state when answering an action in the future.
  5. The initialArg is passed as argument to the init function.
  6. The state property name and the initial value of this state are return by the init function.
function init(initialState) {  return {count: initialState};}
function MyComponent(initialState) {
    const [state, disptach] = useReducer(reducer, initialState, init );
    return (
      // code with dispatch calling…
    )
}
function App() {
    return (
        <div>
            <MyComponent initialState={0} />
        </div>
    )
}

The only way to access the state when using the useReducer hook.

The way to access the current state to either modify it or display it is:  state.propertyName. If we consider the previous example, it would be state.count.

The reducer function structure

The function reducer():

  • is placed between the useReducer import and the component function;
  • receives two arguments : a state and an action;
  • apply a modification on a specific state property of the received state depending on the action.type received by the switch method and return a new state value .

Note: Do not forget to throw an error for the “default” case.

function reducer(state, action) {
    switch (action.type) {
        case ‘type1‘:
            return {statePropertyName: state.statePropertyName + 1};
        case ‘type2’:
            return {statePropertyName: state.statePropertyName – 1};
        // As many cases as types
        default:
            throw new Error();
    }
}

In the above structure example, the state modification is adding 1 or retrieve 1 but that could be any other modification..

The component render with the useReducer dispatch method

The dispatch method which is in useReducer hook called at the top of the component function,

  • is used inside the return(),
  • is passed inside a function which handles the event assigned to a HTML element, like onClick for a button for example,
  • sends an action type to the reducer function.
function MyComponent() {
    const [state, dispatch] = useReducer(reducer, initialState);
    return (
      <div>
        <button
          onClick={() => dispatch({type: ‘typeName’})}>
          Text of the button here
        </button>
        // code…
    </div>
    )
  }

Note: Oviously, id, className and other attibutes are assigned to the button element.

The component render was the last part of useReducer struture we had to discover before using it to create a useReducer counter.

In conclusion

In this article we have discover the useReducer coding structure. More exactly, we have discovered two useReducer coding structures. All the events are handled in the same place: the reducer function.

In a previous article, we have seen how to optimize the components performance with the useEffect hook and the debounce function, the useReducer hook gives also the possibility to optimize the performance of the components triggering deep updating since the dispatch method can be used instead of a callback function.

In the next episode, we will learn how to code a useReducer Counter.

Haut de page