Setting Up Redux with Redux Thunk and Persist in Next.js



Setting Up Redux with Redux Thunk and Persist in Next.js

Managing state efficiently in a Next.js application is crucial, especially when handling user authentication. In this guide, we'll walk you through setting up Redux with Redux Thunk and Redux Persist to manage authentication seamlessly and ensure state persistence across sessions.

Project Structure

Understanding the folder structure is essential for organized development. Here’s the project layout:

project-root/
│── src/
│   ├── pages/
│   │   ├── login.js
│   ├── store/
│   │   ├── actions/
│   │   │   ├── auth.js
│   │   ├── reducers/
│   │   │   ├── auth.js
│   │   ├── store.js
│   │   ├── init.js
│   ├── services/
│   │   ├── auth.services.js
│   ├── config/
│   │   ├── constants.js
│   │   ├── validationSchema.js

1. Installing Dependencies

To get started, install the required dependencies:

npm install redux react-redux redux-thunk redux-persist

2. Configuring the Redux Store

Create a store.js file inside the src/store directory to set up Redux and state persistence:

import { persistReducer, persistStore } from "redux-persist";
import storage from "redux-persist/lib/storage";
import rootReducer from "./reducers";
import { applyMiddleware, createStore } from "redux";
import thunk from "redux-thunk";

const persistConfig = {
  key: "root",
  storage,
};

const persistedReducer = persistReducer(persistConfig, rootReducer);
const store = createStore(persistedReducer, applyMiddleware(thunk));
const persistor = persistStore(store);

export { store, persistor };

Key Takeaways:

  • persistReducer & persistStore: Ensure Redux state persists after page reloads.

  • applyMiddleware(thunk): Enables async actions like API calls.

  • Exports store and persistor: These will be used in Provider and PersistGate in _app.js.

3. Initializing Default State

Create an init.js file inside src/store/ to define the default authentication state:

export const AUTH = {
  isAuthenticated: false,
  token: null,
};

4. Creating the Authentication Reducer

Inside src/store/reducers/auth.js, define the authentication reducer:

import { AUTH } from "../init";

const auth = (state = AUTH, action) => {
  switch (action.type) {
    case "SET_TOKEN":
      return { ...state, isAuthenticated: true, token: action.payload };

    case "LOGOUT":
      return AUTH;

    default:
      return state;
  }
};

export { auth };

Key Takeaways:

  • SET_TOKEN: Updates state with the user's authentication token.

  • LOGOUT: Clears the authentication state.

  • AUTH: Default state imported from init.js.

5. Defining Authentication Actions

In src/store/actions/auth.js, define login and logout actions:

import { toast } from "sonner";
import { login } from "../../services/auth.services";

export const userLogin = (payload) => async (dispatch) => {
  try {
    const response = await login(payload);
    if (response.status === 200) {
      dispatch({
        type: "SET_TOKEN",
        payload: response.data.token,
      });
    }
  } catch (error) {
    if (error.response) {
      toast.error(error.response.data.message);
    }
  }
};

Key Takeaways:

  • userLogin: Dispatches API response data to Redux.

  • Error Handling: Displays error messages using toast.error().

  • Uses Redux Thunk: Handles async operations efficiently.

6. Implementing the Authentication Service

Inside src/services/auth.services.js, create the login API call:

import { MODULES } from "../config/constants";
import { http } from "./http";

export const login = async (payload) => {
  try {
    return await http.post(MODULES.AUTH.LOGIN, payload);
  } catch (error) {
    throw error;
  }
};

Key Takeaways:

  • Encapsulates API Calls: Centralized authentication logic.

  • Handles API Errors: Throws errors for Redux action handling.

  • Uses http Module: Helps maintain cleaner service calls.

7. Building the Login Page

In src/pages/login.js, integrate Redux authentication:

import React from "react";
import { Formik } from "formik";
import { login } from "../config/validationSchema";
import { userLogin } from "../store/actions/auth";
import { connect } from "react-redux";

const Login = (props) => {
  return (
    <div className="bg-base">
      <div className="container h-[100vh]">
        <Formik
          initialValues={{ email: "", password: "" }}
          validationSchema={login}
          onSubmit={(values) => props.userLogin(values)}
        >
          {({ handleChange, handleSubmit }) => (
            <form onSubmit={handleSubmit}>
              <input
                type="email"
                name="email"
                onChange={handleChange}
                placeholder="Email"
              />
              <input
                type="password"
                name="password"
                onChange={handleChange}
                placeholder="Password"
              />
              <button type="submit">Login</button>
            </form>
          )}
        </Formik>
      </div>
    </div>
  );
};

export default connect(null, { userLogin })(Login);

Key Takeaways:

  • Formik for Forms: Handles validation and submission efficiently.

  • Redux Integration: Dispatches userLogin() action.

  • Connects to Redux Store: Uses connect() to pass actions as props.

Comments