Blogs
Code

Mastering React Redux: Build an Authentication System from Scratch

Learn how to implement Redux for state management.
Redux blog

State management is vital in React development. It involves managing and updating the app's state. React is a component-based library with reusable components, each having its own internal state. For example, in an app displaying posts to authenticated users, we need to track the user's authentication state and retrieve posts from an endpoint. To ensure proper functionality, the user's authentication should be tracked across components, and a valid token is necessary for data access. To manage data effectively, we can use Redux, which provides global access.

In this comprehensive guide, intermediate to advanced developers familiar with ES6 and React will learn how to use Redux for state management in React apps. Throughout the guide, you will gain insights into the following topics:

  1. What is Redux?
  2. When and when not to use Redux.
  3. Actions, reducers, and stores in Redux.
  4. Working with asynchronous actions in React.

By properly implementing Redux, you can create more maintainable and scalable React applications.

What is Redux?

Redux is a predictable state container for JavaScript applications. In other words, it is a tool that helps us manage the state in a predictable way. Let's break down this definition into two parts:

What is a state?

State refers to all the data that your application needs in order to function properly. For example, in the code snippet below, we have an example of the state:

How is Redux predictive?

Redux enforces a strict set of rules that can be used to make changes to the state in a predictable way. In the code snippet below, we have a rootReducer function that contains a switch statement that handles different types of actions:

From the logic above, we can clearly predict how each action affects the state of our app. We can have a central place called a store that holds our state, and we can manipulate it with functions called reducers that we define.

Store, Actions & Reducers in Redux

Now that we know what redux is and how we can use it. Let's dive deeper to explore the three main concepts in it: store, action, and reducer.

Store

The store is the central hub of Redux. It holds the application state and provides methods to do the following:

  1. Return the current state of the store.
  2. Dispatch actions to the reducer to update the state.
  3. Add listeners to functions that will be called whenever the state changes.

The store is created by passing a reducer function to the createStore method.

In the code above, we have an initial state object appState, which contains two properties: user and posts. We then define a rootReducer function that takes the current state and an action as arguments and returns a new state based on the action. Finally, we create the store by calling createStore with the rootReducer.

Action

An action is a plain JavaScript object that describes a change in the application state. It has a type property that describes the type of action and an optional payload property that can contain additional data. In Redux, actions are created using action creator functions. Here's an example:

In the code above, we define three action creator functions: loginAction, logoutAction, addPostAction, and deletePostAction. These functions return an action object with a type property that describes the type of action and an optional payload property that contains additional data.

Reducer

A reducer is a pure function that takes the current state and an action as arguments and returns a new state. In the context of Redux, a pure function is a function that given the same inputs, will always return the same output, and has no side effects. In other words, a pure function doesn't modify its input arguments and doesn't modify any state outside of its scope.

The reducer describes how the state should change in response to an action. Reducers are the only way to change the state in Redux. Here's an example:

We define userReducer and postsReducer as reducers. Each takes state and action, returning a new state based on the action. combineReducers combines them into rootReducer.

userReducer handles LOGIN and LOGOUT actions. For LOGIN, it returns a new state with loggedIn as true and a token from the action payload. For LOGOUT, it returns a new state with loggedIn as false and the token as null. If the action is unrecognized, it returns the current state. The postsReducer handles ADD_POST and DELETE_POST. ADD_POST returns a new state with existing posts and the new post from the payload. DELETE_POST removes a post matching the payload id. If the action is unrecognized, it returns the current state.

Redux's pure functions guarantee predictable state changes. Reducers always return new state objects, not modifying the original. This ensures easier tracking and maintaining a consistent application state..

Getting Started With Our Project

Now that we have covered the core concepts of Redux, it's time to tie everything together by building an application to solidify our knowledge. To get started, we can use the following command, depending on the stage of our app:

  1. Creating a new project:

    These commands are the recommended way to start new apps with React and Redux. They provide some starter code that includes examples of how Redux Toolkit works.

  2. Adding Redux to an existing project:

    For the purpose of learning, we highly recommend starting a new project to follow along easily. If you have already used the command above to create your app, please delete everything in the src folder to ensure we follow a uniform approach going forward.

By following these steps, we can start building our application with a solid understanding of Redux and Redux Toolkit. This hands-on approach will help us reinforce our knowledge and apply our skills to real-world scenarios.

Creating A Store In Redux

A few sections ago we discussed what a store is and its purpose. Now, let us see how easy it is to implement it with Redux Toolkit. Let's follow the following steps:

  1. Create a new folder in src and call it app.

  2. Inside our newly created folder create a new file called store.js.

    In the file you have just created. Copy and paste the code below:

    Let's explain the code a little more:

    1. The code imports configureStore from @reduxjs/toolkit. configureStore helps us create a Redux store with default settings provided by Redux Toolkit.
    2. The reducer argument of configureStore is an empty object, which means there are no reducers defined yet. Recall the a reducer is a function that helps us modify our applications state. Later when we define our reducers we will pass them to the reducer argument which is an object.
    3. The code exports a store object created with configureStore. This simply makes it possible to import our object from any file of our choice.

The above steps create our redux store. It's interesting to note that the Redux Devtools extension has also been automatically configured, making it possible to inspect the store while developing.

Making Store Available To React

Our store can only be useful if it can provide data to the entire application. Here we will learn how to avail it. Follow the following steps:

  1. Create a new file in src called index.js.

  2. Copy and paste the following into this file:

    This code sets up the basic configuration for integrating Redux with a React application. The Provider component is imported from react-redux and wraps the root component of the app, providing the Redux store to any child components that need it. The store is imported from a local app/store.js file that was created earlier and initialised using configureStore() method from @reduxjs/toolkit.

    The ReactDOM.createRoot method is called to create a new root for the React tree to be rendered into. Finally, the render() method is used to render the Provider component with the store passed in as a prop. This sets up the foundation for the application to use Redux for state management.

Creating Redux State Slices

Before looking at how to implement a slice, let's define it. A "slice" in Redux Toolkit is like a mini Redux store that manages a specific part of your application state. It contains all the code related to handling actions and updating the state for that specific part, and can also include any helper functions or selectors that are specific to that part of the state. 

Slices are created using a function called createSlice, and they allow you to keep your code organized and modular by breaking down the state management into smaller pieces. Let's see how we can achieve this by following a few simple steps:

  1. In src create a new folder called features.

  2. Create A folder called user and a file named userSlice.js:

    This code uses createSlice from @reduxjs/toolkit to create a Redux slice named user. It has initial state properties loggedIn, user, loading, and error.

    Reducers define state updates. For instance, checkLoginStatus sets loggedIn and user based on user data in localStorage.

    The loginStart sets loading to true and clears previous errors. loginUser is an async action creator that dispatches loginStart, sends a login request via Axios, and dispatches loginSuccess or loginFailure.

    Actions correspond to reducer functions and can be dispatched to the Redux store.

    The slice reducer and actions are exported for Redux store usage.

Adding Slice Reducers to the Store

Now that we have all our reducer functions, let's add them to the store. We will do this by modifying our store.js file. In our reducer augment simple do the following:

This code is importing one reducer (userReducer) from a file located in the "user" directory.

The configureStore function is called with an object argument that has a reducer property, which is also an object. This reducer object maps strings (representing the state slice names) to reducer functions. In this case, userReducer is assigned to the user slice.

In summary, this code sets up a Redux store with one slice: user. The user slice will be handled by the userReducer function.

Let us now conclude by using everything we have created to handle user authentication. To do this follow the following steps:

  1. Create a new file in src called App.js

  2. In this newly created file copy and paste the code below:

    App is a React component using Redux for user authentication. It imports functions from userSlice to manage login/logout and check login status. The component uses useSelector to access specific data from the Redux store and useDispatch to access the dispatch function.

    State variables for username and password are defined using useState. Functions like handleSubmit and handleLogout dispatch relevant actions.

    The useEffect hook dispatches the checkLoginStatus action on the component mount. Based on loading and loggedIn flags, it renders loading, welcome message with the user's name and logout button, or a login form.

    For more details on useSelector and useDispatch, you can refer to the following links:

    1. useSelector documentation
    2. useDispatch documentation
  3. Finally, update your index.js file so that it can render App.js. Simply import App.js in the file.

Run npm start to see how your authentication system is working. By doing all these steps you have successfully created your auth system that is managed by Redux. In addition, we are persisting with the user's login state. Before we come to a close let's jump into one last section.

Should I Always Use Redux?

It depends on the specific needs of your application. We can conclude this section by considering some pointers that can help us decide on the type of state management that we should consider when developing an application:

When to use Redux

  1. The application has a large amount of state that needs to be shared across multiple components and/or pages
  2. There are complex interactions between different parts of the application that require a lot of coordination
  3. The application has a lot of asynchronous data fetching and updates
  4. You need to be able to time-travel through state changes for debugging purposes

When not to use Redux

  1. The application has a smaller amount of state that needs to be shared across multiple components
  2. The state is relatively simple and doesn't require a lot of complex interactions between different parts of the application
  3. You don't need advanced debugging tools like time-travelling
  4. You want a simpler and more lightweight solution than Redux

So, consider using Redux for relatively complex projects and Context API for small applications. Making reference to the above points can help you decide on what you should use for your next project.

React Redux Works great when the codebase is modular and split into components. For this, you can use the Locofy.ai plugin to generate modular, and highly extensible React components directly from your Figma & Adobe XD design files.

You can use the auto layout feature on Figma to make your designs responsive on the Locofy.ai plugin and even if your designs don't utilise auto layouts, the plugin offers a Design Optimizer feature that uses AI to apply auto layouts to your design files. Once your designs are responsive, you can use the Auto Components feature to split your design elements into working React components, making them easy to extend.

Keep your loco mode on!
Stay updated on the latest Locofy announcements and product updates

© 2024, Locofy Pte Ltd. All Rights Reserved.