useState or useReducer – which to choose for your application

useState or useReducer – which to choose for your application

useReducer helps us manage state in a complex application. What exactly is useReducer? Is it an alternative to useState? useState and useReducer are both used to handle state logic. It is thereby necessary to understand when to use these hooks. useReducer is not replacing useState in any way but it would be more efficient to use useReducer in some complex applications.

We are going to explore these hooks in detail showing the best hook to use in any given application and how we can convert one of these hooks to use the other. Let’s get started.

The commonly used state hook – useState, is a react hook that lets you add a state variable to your component. It is usually written like this:

const [state, setState] = useState(initialState)

useState takes in an initialState which can be a string, boolean, number, array, object or function. It returns two values namely:

  • state – This is the current state

  • setState – This is a function that updates the state.

A simple example looks like this:

const [country, setCountry] = useState('Nigeria');
const handleClick = () => {
    setCountry('South Africa');
}

The above code snippet is a simple example of the useState hook and how we can use it to update the state. If the handleClick function is passed to a button, on clicking the button, the application re-renders to show the update – “South Africa” for the country state variable.

What exactly is useReducer?

useReducer is a React hook that lets you add a reducer to your component. So, what is a reducer? A reducer is a function that allows us to specify all the state update logic in a single function.

useReducer takes in two major parameters and an optional third parameter. It returns two values – state and dispatch. The state is the current state and the dispatch is a function that lets you update the state.

const [state, dispatch] = useReducer(reducer, initialState, initFunc?)

Same simple example with useReducer:

const reducer = (state, action) => {
    switch (action.type) {
        case 'change_country': {
            return {
                country: action.newCountry
            }
        }
        // other cases are written here
        default: {
            return state
        }
    }
}

const [state, dispatch] = useReducer(reducer, {country: 'Nigeria'});

const handleClick = () => {
dispatch({
    type: 'change_country',
    newCountry: 'South Africa'
})
}

An explanation of the above sample code snippet – we invoked our useReducer which accepts a reducer that contains the logic for updating the state and an initial state of { country: ‘Nigeria’ }. We are calling the dispatch function in the click handler; passing in the type of action we want to perform with the value of the next state.

Dispatch and Reducer Explained

Dispatch Function: The dispatch function returned by useReducer lets you update the state to a different value and trigger a re-render. You need to pass the action as the only argument to the dispatch function. An action is usually an object with a type property identifying it and, optionally, other properties with additional information.

dispatch({
    type: ‘change_country’,
    // other properties
})

A reducer function is declared like the below code snippet. It accepts state, which is the current state and action which updates the state and returns the next state.

function reducer(state, action) {
    // state updates are made here
}

We have successfully updated a state using both useState and useReducer. So, of what use is one hook over the other? We will cover this next in this article.

Comparing useState and useReducer

  • useReducer is similar to useState. useReducer enables us to move our state update logic into a single function which is more efficient for larger applications.

  • useState is simple and easy to set up for smaller applications but when the application gets complex, it becomes difficult to read with many state logic in event handlers. useReducer proves to be efficient for this use case as it helps organize our state logic in one place.

  • useReducer is easier to debug in a complex application as you can easily find the action that is not dispatched, but with useState, you would have to look through a lengthy code to identify the error.

  • Examining the above code sample, using useState resulted in fewer lines of code. While this is good for smaller applications when the application gets larger, the lines of code also increase. Using useReducer to separate the logic in a function is a better approach for larger applications.

If your application is handling multiple state logic in event handlers, then you should use useReducer else useState is fine.

How to migrate from useState to useReducer.

We are going to explore an example that uses both useState and useReducer. You will, therefore, see how we can migrate from one to the other. Here is a simple application that gets the user’s first_name and last_name with a button to add the names to the existing name list and a button to reset the name list.

Steps to follow:

  • Move from setting state to dispatching actions.

  • Write a reducer function.

  • Use the reducer from your component.

The above is the code sample of our application. To see the implementation of our state update logic using useState, comment out the FormUsingReducer component in App.js and uncomment the Form component in App.js and vice-versa for useReducer.

Conclusion

We have explored both useState and useReducer to update state. We also looked at a real live example, when it is preferable to use these hooks and lastly a quick comparison of both. A lot of references are from the React documentation. Check it out here.

Understanding the concepts we use and exploring new concepts is critical for growth as developers. I hope you have learned something and you are excited about applying what you have learnt in your next project.

Thanks for reading.