The Best Way to Manage Application States in React With Redux

Learn how to easily connect all your components to a single state tree in a robust and pleasant way.

Arthur Effting
The Startup
8 min readMay 14, 2020

--

Those of us nerds developing complex web applications have certainly found themselves looking for a solution to simplify state management across the whole application at some point. Discovering Redux was the light at the end of the tunnel for me, and I hope it can illuminate your path as well.

Requirements

Assuming you’ve already set up a Node development environment, here is a list of everything you’re going to need to successfully finish this tutorial.

React app with Typescript

If you are new to React, I suggest taking a look at the documentation and learning how to create your first React application with create-react-app before moving on. I’ll be typing my components in this tutorial, so a little knowledge of Typescript won’t hurt.

React Redux

Redux is the framework you will be learning to use to maintain your application state-tree. More on that later on.

Middlewares

There’s a variety of middleware that can be attached to your Redux store to enhance or enable extra functionality. In this tutorial, we will be using one for logging and another one for enabling async logic to interact with the store.

Material-UI (Optional)

For aesthetic reasons, I’ll be using Material-UI to style my react components. This is not a part of the tutorial, though, and you can nevertheless find the full code in the repository.

The project

Our goal is to build a simple login form that, given username and password, transitions to a welcome screen that lets the user logout. Sounds doable.

Define what your application state looks like

The first step is describing what fields are relevant to your components, and what kind of stuff you want to be stored locally. This can be done by defining interfaces in TypeScript.

Even though our application is very simple, I will divide its state into two sub-trees so that you get a feeling for how you can define separate reducers for each section of your state. This is what our entire application state looks like.

In what I will call the view state, I will simply store a boolean indicating whether the authentication is currently being processed or not.

The authentication state will indicate whether a user is currently logged in and, if so, his username. Needless to say that in a real-world scenario storing authentication credentials would look very different from this.

Once you have the shape of your state defined, it is very useful to also store what the initial state of your application should look like. In our case, we are neither authenticated nor authenticating when the page is first loaded.

Actions

All interactions with your application state happen by dispatching actions. These action objects are passed to functions called “reducers”, that interpret them and update your state accordingly. We will be defining our reducers later down the line.

There is a field that has to be present in all actions dispatched to your reducers: type. Other than that, the structure of your actions is up to you. It is a good idea to define all possible action types in string-based enums, this will let you have a better idea of what is happening to your state when logging it.

It is also a good idea to export your actions as functions so that the general structure of what the action object looks like is kept separately. The only action type needed to interact with our view state is one to tell our components whether the application is currently loading or not:

Similarly, for updating the authentication status the only actions needed are to log in and logout:

Since we want our view state to change whenever a login attempt is made, we want to set the loading status to true whenever the user is authenticating. This could also be achieved by dispatching multiple actions from our components, but it can be done better by making use of one of the middleware installed in the first section.

By itself, Redux does not allow asynchronous logic when dispatching actions. Thunk lets us have our actions themselves dispatch other actions when executing by returning a function of dispatch instead of a plain object.

That way, creating the loading logic we’ve just described is way easier:

In the code above, authenticate is simply an asynchronous function that receives the username and password and returns a boolean value indicating whether the login attempt was successful or not. Here’s a mock-up:

Reducers

Reducers dictate how your states change when actions get dispatched. We will create a reducer for each of the sections in our application state.

A reducer is simply a function that, given the previous sIApplicationStatetate and the action dispatched, returns the updated state. We can use the initial state we defined before to define the default value for the state parameter of our reducer.

The view reducer is very straightforward. There is only one possible action type, which makes our life easier:

The authentication reducer has to act according to the action type:

The Store

Now that we have described exactly how our application state looks like and how it responds to all possible actions within our domain, we can create the Redux store that will handle all of it.

First, instantiate the middleware aforementioned.

You need the thunk middleware for the asynchronous action we wrote before and the logger so that you can keep up with what’s happening to your state. It lets you see, upon every action dispatched, how the state has been modified.

Since we are using different reducers for each part of our application state, we need to combine them in a single reducer. Using the function provided by Redux, indicate what reducer should be used for each field of your state.

Now that we have our middleware enhancer and the root reducer created, we can finally create the store to manage it.

Providing the store

Now that you successfully created your store, it’s time to plug it into your application. Go to the top-level rendering function of your app and wrap your application by instantiating a provider for your Redux store. For clarity, I’ve wrapped all the steps of the previous section in the configureStore() function.

Connecting components

Everything instantiated within your provider can connect to your store to access the application state and dispatch actions to change it.

In our case, we have a single Home component at the center of the rendering function. For this component, I’ve used a container pattern to separate UI logic from state management. You can take a closer look at this pattern here. Let’s take a closer look at the component, here are the props for the layout:

  • onLogin: function to be executed when the login button is clicked
  • onLogout: function to be executed when the logout button is clicked
  • loading: boolean indicating whether authentication is happening
  • authenticated: boolean indicated whether the user is logged in
  • username: current user’s username, if authenticated

I will skip giving details about the layout of our component since it is not the aim of this tutorial to teach you how to style elements. You can nevertheless find the full code for it here.

What does matter, though, is the container for our layout. This is where the component gets connected to our store, and our application state is turned into properties to be passed to it.

Connecting to the Redux store requires two functions:

  • mapStateToProps: transforms the application state into props to be passed down to your component. This is mainly used for specifying what part of the application state is relevant to your component.
  • mapDispatchToProps: this can be used to create logic that depends on the dispatch function of your store so that you can create functions to interact with your state and pass them to your component as props.

Here’s how our component will see the application state:

Here’s how it will be able to dispatch actions:

Now that you have both functions, you can create a connector and wrap your layout with it. (Note that in this case my layout definition matches exactly the props generated by the mapping functions, otherwise you need to some extra wrapping)

Now you have a fully connected Home component that can access your application state and dispatch actions to change it. In case you want to export the type of your connector to include it in your layout definition, you can do that as well using ConnectedProps.

All done

If you’ve implemented your own layout or copied mine, you should now have the fully functional login form ready to use, start your application and take a look. I recommend taking a look at the developer console to see what exactly happens when your actions get triggered so that you get a feel for it.

What’s next?

Check out my article on how to save your application state to local storage using redux-persist. This way your user doesn’t have to re-enter credentials every time the page is reloaded!

Resources

--

--