A Simple Reducer Example with TypeScript, No Need for Redux or MobX

In this post, we'll cover React Reducers (with the useReducer hook). This tutorial will help you understand:

  • what reducers are

  • why you need reducers at all

  • how to use the useReducer hook

  • how to easily implement them in your React code with TypeScript

  • know when to use advanced options like Redux and MobX

By the way, this is an intermediate React topic. We cover this more in depth in Fullstack React with TypeScript.

We just added a bunch of new content on how to use SSR with Next.js to the book. Check it out.

Why React Uses Reducers#

As our React applications grow into full-fledged interactive apps - the amount of data -- and state -- they work with increases.

Keeping track of state, and managing this state, creates a need for a layer of our app that deals solely with this important task.

This is where reducers come in.

What is a Reducer?#

All state management solutions perform some (or all) of these tasks:

  • keep track of the current state

  • take in new information that causes state modification

  • process the dispatched information and calculate a new state

  • return this new state so the application can use it

Reducers are a type of function that can perform these task for state management

Because Reducers are built into the React API, you don't necessarily have to use a separate state-management library like Redux or MobX.

Using reducer functions can lead to simpler code, as well as avoid the overhead of a separate state management library.

Reducers Illustrated#

Suppose that we're tracking the name of a person in our state.

A reducer that manages this state must:

  • Remember the old state

  • Accept "dispatched" actions

  • Determine a new state and

  • Return a new state

Here is a diagram:

The dispatched information is called an "action." This reflects that some action or event in the application has happened, and that's why we must now update the state.

For example, think of an action as a user submitting a form, clicking on a button, selecting an option, or other actions that prompt us to change the state in the application.

Let's Create Our Reducer#

We are going to create a restaurant order page that allows a diner to select a dish from several options on the menu.

Make sure you have npm installed, then, to create our app, run npx create-react-app restaurant --template typescript.

Our restaurant app will use React to display a menu to the user. When the user selects an item from the menu, we want to automatically change the state of the app to reflect the user's selection.

To add a reducer, React provides us with a hook called useReducer.

It works exactly as it sounds: React's useReducer is a function that allows us to specify the reducer we want to use, and the reducer will go into action to compute the new state of our application!

Here is the repository with the full code for using reducers for React state management with TypeScript. You can also try out the demo of the app on CodeSandbox. The main file to pay attention to is src/App.tsx.

Code Walkthrough#

At the very top of our App.tsx file, we import React and its useReducer hook using the line:

To implement our reducer function, we must supply a state object and an action.

The reducer will use this old state object and the dispatched action to figure out what the new state should be. It will then return a new state object for the application to use.

Here is our reducer function that does exactly this:

In this case, state is the old state we want to replace, and action is the action fired off from the user interface when a user selects a different menu item.

TypeScript allows us to set types for both the state and action to mitigate against us making type errors in the code.

If you look at the function definition above, you will see that our function arguments are accompanied by types. These types are defined briefly in the file src/App.tsx

Using the Reducer to Manage State#

We've defined the reducer function above, but we need to actually assign it as the manager of our application's state.

React provides the method useReducer(reducer) to enable us to select the reducer to use.

Since our reducer is stored in a function with the name reducer, we can just assign it as the reducer for React to use with the call useReducer(reducer). However, when we call useReducer, the useReducer hook returns a state and a dispatch method that will fire actions to change the state dynamically.

So we need to store the returned state and the dispatch method. We do this with the following array destructuring assignment:

Note: This useReducer assignment takes place inside our component. Check out the full component code here.

Firing Actions in Response to User Interaction#

Finally, we include logic to fire off actions to the reducer.

These actions will be triggered by the user's interaction with the app. These actions are what lead to state changes in response to the user's selection of different menu items.

Here's the code that fires off an action when the user selects from the available menu items.

What this means is that, if a user selects a "pizza" from the menu, or a "burger", an action will be dispatched with that value as its type. This type is then assessed by the reducer, which takes the action and the old state of the app, then returns a new state for the app.

React will then update the user interface automatically once the state has been changed by our reducer!

And with that, you are ready to dive more into the code.

Of course, if you want to learn how to use reducers with React, as well as testing with TypeScript, common best-practices and patterns, and using SSR with Next - then you should check out Fullstack React with TypeScript: