Setting up Redux Persist with Redux Toolkit in React JS
Tags: react, redux, frontend, javascript
Published: 2023-11-03 23:27:10


![Redux banner image](https://upload.wikimedia.org/wikipedia/commons/3/30/Redux_Logo.png)

State management is a fundamental aspect of building modern web applications. As an application grows, managing and maintaining the states can become rather daunting. [Redux](https://redux.js.org/), a popular JavaScript library, has been the go-to solution for state management in React applications for a while now. 

However, Redux, or pure Redux to be specific, can be quite verbose and boilerplate-heavy. It requires a significantly lengthy setup, which is where [Redux Toolkit](https://redux-toolkit.js.org/) comes in handy, offering a simplified and more efficient way to set up and manage state in your React applications.

### What is Redux Toolkit?

Redux Toolkit is a much simpler way to set up and use Redux without the need to create large amounts of boilerplate.

### Redux Persist

Redux helps applications hold onto data when navigating from page to page, which is known as the global state. However, as most people who are familiar with Redux will know, the global state will be reset the moment the application is reset.

### Using Redux Toolkit with Redux Persist

The following tutorial will implement a simple login system, to fetch data to store it in Redux and into the localStorage (default) with Redux Persist. This way, routes can be restricted depending on the existence of data being present in the browser storage. 

Custom hooks can do all of this, but the behavior may not always be consistent on all browsers, and the implementation can lead to UI inconsistencies. Redux Persist ensures that the app does not get loaded before the available data is fetched from the browser memory, and then lets the logic flow as it is implemented.

To follow along and understand this tutorial properly, here are some topics that you should have some idea about beforehand:

1. React JS
2. Redux
3. Redux Toolkit

### Setting up the React JS Project

First of all, the React project must be set up, and I'll use Vite to do this as it is easy to use. Run the command below: 

```bash
> npm create vite@latest
```

You will be given a couple of prompts, such as the name of the project, which can be whatever you want. I named mine 'auth-redux'. Next, the prompt will be given to select the framework, I went with React and then when prompted for a variant, I selected JavaScript.

```bash
✔ Project name: auth-redux
✔ Select a framework: React
✔ Select a variant: JavaScript
```
After the setup is created, run the command:

```bash
> npm install
```

This will install the necessary `node_modules` to get you started. Finally, to see the server go live, run the command:

```bash
> npm run dev
```

### Installing Redux, Redux Toolkit and Redux Persist

With your server set up, it's time to install react-redux, redux-toolkit, and redux-persist, along with react-router-dom, which will be needed for navigating pages in React. We will also install Sass, to use `.scss` files

```bash
> npm i react-router-dom react-redux @reduxjs/toolkit redux-persist sass
```

Once the setup is complete, we can proceed to create the project itself.

### Creating the Login Module

For those who want to check every file and module used in the project, the code is available [here](https://github.com/snigdho611/auth-redux) for you to follow along with. There is also a TypeScript version in a separate branch for those who require it. 

```
--src/
  --assets/
  --components/
    --common/
      --Navbar
        --index.jsx
        --index.scss
    --pages/
      --Home
        --index.jsx
        --index.scss
      --Login
        --index.jsx
        --index.scss
  --App.css
  --App.jsx
  --main.jsx
```

**The project** - two pages, a home page, and a login page. 
**The goal** - to ensure that after logging in, the user is redirected to the home page. If the home page is attempted to be loaded manually in the browser by hitting the URL without logging in, it will redirect back to login. We will try to check and keep track of the login status using Redux and localStorage.

Creating a login form and a home page is fairly simple if you know React JS and CSS/SCSS. Here are the two pages I have implemented. 

Log in:

![Log in Page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/91zayi8z7w2am4byek1a.png)

Home:

![Home Page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i6hv8fvas9mlvxyfnd6d.png)

### The Redux and Redux Persist Setup

Once the pages and necessary components are set up, we can move to explaining the Redux logic. I will be implementing everything related to Redux inside a folder called `store`.

First, we'll create a "slice" for the initial state of Redux. You can read more on creating Redux Slices [here](https://redux-toolkit.js.org/api/createslice). Essentially, it will be an object, that will contain some properties as null.

```javascript
const initialState = {
    id: null,
    username: null,
    firstName: null,
    lastName: null,
    gender: null,
    image: null,
    token: null,
};
```

We can then use this to create our slice. We will create a couple of reducer functions to implement our logic for saving data when a user logs in, and removing data when a user logs out. This can be done with the syntax:

```javascript
import { createSlice } from "@reduxjs/toolkit";

const initialState = {
    id: null,
    username: null,
    firstName: null,
    lastName: null,
    gender: null,
    image: null,
    token: null,
};

export const authSlice = createSlice({
    name: "auth",
    initialState,
    reducers: {
        saveLogin: (state, action) => {
            state.id = action.payload.id;
            state.username = action.payload.username;
            state.firstName = action.payload.firstName;
            state.lastName = action.payload.lastName;
            state.gender = action.payload.gender;
            state.image = action.payload.image;
            state.token = action.payload.token;
        },
        removeLogin: (state) => {
            state.id = null;
            state.username = null;
            state.firstName = null;
            state.lastName = null;
            state.gender = null;
            state.image = null;
            state.token = null;
        },
    },
});

export const { saveLogin, removeLogin } = authSlice.actions;
export default authSlice.reducer;
```

With the slice created, we can now move on to create the store itself and the logic to persist the data. Now, we will create the universal reducer using `combineReducers` function from redux toolkit. 

```javascript
import { combineReducers } from "@reduxjs/toolkit";
import authSlice from "./auth";

const reducers = combineReducers({
    auth: authSlice,
});
```

Next, we have to set up the Redux Persist mechanism that will be able to retain the data using localStorage. We can do this by creating an object for the persist configuration, and it will require a key and a storage value.

```javascript
const persistConfig = {
    key: "root",
    storage: localStorage,
    whitelist: ["auth"],
};
```

Almost done, now using the reducers and persistConfig:

```javascript
const persistedReducer = persistReducer(persistConfig, reducers);
```

Creating the store and the persistor objects to export them:

```javascript
const store = configureStore({
    reducer: persistedReducer,
    middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({
        serializableCheck: {
            ignoredActions: [PERSIST],
        },
    }),
});

const persistor = persistStore(store);
export { store, persistor };
```

The persistor object is what will now be able to ensure that the initialState is created as soon as the app is loaded in the browser, and afterward, Redux Toolkit can handle the further functionalities.

In `main.tsx`, which is the parent of `App.jsx`, we will write the following:

```jsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import { persistor, store } from './store/index.js'
import { Provider } from 'react-redux'
import { PersistGate } from 'redux-persist/integration/react'

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <Provider store={store}>
      <PersistGate loading={null} persistor={persistor}>
        <App />
      </PersistGate>
    </Provider>
  </React.StrictMode>,
)
```

We are wrapping the App component in the `PersistGate` to ensure that the App does not load until the data fetch/reception is completed between the application and the browser storage by passing the persistor object it that we created earlier. I have set the loading value to null, but you can use this to implement loading screens/components before data is properly loaded into Redux from storage. 

And over that, we are wrapping the `PersistGate` component in the `Provider` component from Redux, passing the store as a prop so Redux Toolkit can handle the basic functionalities.

### How the Private Route Logic Works

_App.jsx_
```jsx
import "./App.css";
import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
import Login from "./components/pages/Login";
import Home from "./components/pages/Home";
import PrivateRoute from "./components/common/PrivateRoute";
import { useSelector } from "react-redux";

function App() {
  const auth = useSelector((state) => state.auth);

  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Navigate to={"/login"} />} />
        <Route path="/login" element={!(auth.username && auth.id && auth.token) ? <Login /> : <Navigate to={"/page"} />} />
        <Route element={<PrivateRoute />} >
          <Route path="/page" element={<Home />} />
        </Route>
      </Routes>
    </BrowserRouter>
  )
}

export default App
```

_PrivateRoute_
```javascript
import { Navigate, Outlet } from "react-router-dom";
import { useSelector } from "react-redux";

const PrivateRoute = () => {
    const auth = useSelector((state) => state.auth);
    return auth.username && auth.id && auth.token ? <Outlet /> : <Navigate to={"/login"} />
};

export default PrivateRoute;
```

Here, the PrivateRoute component is going to check if the username, id, and token exist or not. Even if the app is reloaded, the `PersistGate` along with the `persistor` object will make sure that the data is fetched from the localStorage in the browser. It will then allow navigation to the `<Outlet />` (Whatever component is supposed to be accessible), otherwise it will navigate back to the login page. 

### Redux Toolkit and Redux Persist at Work

Using the Inspect tab to monitor the data in the local storage, on first load, you can find that the data of the initial state is saved here in the login page as all properties set to null in the auth object.

![Before logging in](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/02lzvft57bclfivqhfbw.png)

After logging in, the data is updated accordingly to the response that is being sent from the login API. Every time you try to navigate to the login page now, you will be redirected back to login.

![After logging in](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yvsyb2lh7ftwpiarfrzy.png)

### Conclusion
Redux can be fairly complex to work with, but Redux Toolkit makes it a lot easier when setting it up and creating the reducers. Redux Persist lets saving and fetching data into browser storage much more easily and conveniently and allows React applications to depend on browser storage when creating private routes to stop users from accessing certain features - even after hard reloading the application.

All of the source code for this project are available [here](https://github.com/snigdho611/auth-redux).