React Advanced State
Learn how to handle advanced state management in React by transitioning from useState to the useReducer hook. Understand reducers, action types, and dispatch functions to manage complex state transitions more predictably in your React apps.
We'll cover the following...
All state management in this application makes heavy use of React’s useState Hook. However, for more sophisticated needs, you can explore micro state management with React hooks using React’s useReducer hook. Since the concept of reducers in JavaScript can be complex, we will focus on the practical implementation to help you manage state logic more predictably.
We’ll move the stories state management from the useState hook to a new useReducer hook. First, introduce a reducer function outside of your components. A reducer function always receives state and action. Based on these two arguments, a reducer always returns a new state:
A reducer action is often associated with a type. If this type matches a condition in the reducer, do something. If it isn’t covered by the reducer, throw an error to remind yourself the implementation isn’t covered. The storiesReducer function covers one type, and then returns the payload of the incoming action without using the current state to compute the new state. The new state is simply the payload.
In the App component, exchange useState for useReducer for managing the stories. The new hook receives a reducer function and an initial state as arguments and returns an array with two items. The first item is the current state; the second item is the state updater function (also called dispatch function):
The new dispatch function can be used instead of the setStories function, which was returned from useState. Instead of setting state explicitly with the state updater function from useState, the useReducer state updater function dispatches an action for the reducer. The action comes with a type and an optional payload:
The application appears the same in the browser, though a reducer and React’s useReducer hook are managing the state for the stories now. Let’s bring the concept of a reducer to a minimal version by handling more than one state transition.
So far, the handleRemoveStory handler computes the new stories. It’s valid to move this logic into the reducer function and manage the reducer with an action, which is another case for moving from imperative to declarative programming. Instead of doing it ourselves by saying how it should be done, we are telling the reducer what to do. Everything else is hidden in the reducer.
Now the reducer function has to cover this new case in a new conditional state transition. If the condition for removing a story is met, the reducer has all the implementation details needed to remove the story. The action gives all the necessary information, an item’s identifier to remove the story from the current state and return a new list of filtered stories as state.
All these if else statements will eventually clutter when adding more state transitions into one reducer function. Refactoring it to a switch statement for all the state transitions makes it more readable:
What we’ve covered is a minimal version of a reducer in JavaScript. It covers two state transitions, shows how to compute current state and action into a new state, and uses some business logic (removal of a story). Now we can set a list of stories as state for the asynchronously arriving data, and remove a story from the list of stories, with just one state managing reducer and its associated useReducer hook.