Web Development Tutorials

Working with Redux in Next.js

June 1st, 2023 | By Jay Raj | 12 min read

Redux is a state management tool for JavaScript applications. In this article, we'll be using Redux with the Next.js app, but it can be used alongside any JavaScript framework. Today's tutorial is about working with Redux in Next.js.

Redux provides a central store for the application state, providing a proper way to update the store values and fetch them. With a centralized store, it becomes easier to write applications and manage the state.

From official documentation, Redux is:

A Predictable State Container for JS Apps. Redux helps you write applications that behave consistently, run in different environments (client, server, and native), and are easy to test.

 

Why do we need Redux?


One of the primary reasons for using Redux is to make it easy to share data between two components. This really comes in handy when the components we are trying to share data between are far away from each other in the component tree.


It creates a central store that is accessible from all over the application. Since it's a central store, it becomes easier to understand how or when the state of the application has changed, and hence it's predictable.


Using Redux in Next.js App


Creating the Next.js App


For working with Next.js, you'll need Node.js 16.8 or above. Install it if you already don't have it installed. We'll be using `create-next-app` to create our Next.js app, and we'll be using TypeScript in our project.

Create your app using `create-next-app`:

npx create-next-app next-redux-app --typescript


Once the installation starts, it will ask a few questions:

  • Would you like to use ESLint with this project? ***No*** 

  •  Would you like to use Tailwind CSS with this project? ***No***

  • Would you like to use `src/` directory with this project? ***Yes***

  • Would you like to use the experimental `app/` directory with this project? ***No***

  • What import alias would you like configured? ***Press Enter***


Enter the answers as shown in the above list and it will create your Next.js app with the required dependencies installed.

Navigate to the project directory and start the project.

cd next-redux-app
npm run dev


Point your browser to http://localhost:3000 and you will have the Next.js app running with the boilerplate code.

Adding Redux to Project


You'll be needing a Redux toolkit. Let's start by adding it to the project.

npm install @reduxjs/toolkit --save


The `--save` option adds the package dependency to the `package.json` file.

You'll also be using `next-redux-wrapper` since we are working with Redux with the Next.js framework.


Creating Slice

A state is an object that contains information about a component, and a `slice` is actually a portion of the state representing a particular feature. Multiple slices come together to create a Redux state.

Create a folder called redux inside your `src` folder and add a file called `cartSlice.ts`. This slice is related to the Cart feature and may contain information related to the cart like the total amount, the total number of items, etc.

Inside `cartSlice.ts` start by importing `createSlice` from `reduxjs/toolkit` which will be used for creating the slice.

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


Next, define a default state or initial state for the `cartSlice`.

// ## CartState Interface
export interface CartState {
    itemsInCart: number;
    totalAmount: number;
}
// ## Define the initial state of Cart State 
const initialState: CartState = {
    itemsInCart: 0,
    totalAmount: 0
};


Now, using the initial state, let's create the slice.

export const cartSlice = createSlice({
    name: "cart",
    initialState,
    reducers: {
        setItemsInCart(state, action) {
            state.itemsInCart = action.payload;
        }, 
        setTotalAmount(state, action) {
            state.totalAmount = action.payload;
        }
    }
});


`createSlice` takes in an object where you need to specify the name of the slice, the initial state of the slice, and `reducers`. `reducers` are functions that help in updating the state. In the above code, you can see `setItemsInCart` and `setTotalAmount` which help in updating the respective state variables.

`createSlice` returns an object as a response that has `actions`,`name`, and `reducer`. `actions` in response is the same function name as passed in `reducers` during creating a slice. You'll need to export it so it can be used for setting the state value.

export  const { setItemsInCart, setTotalAmount } = cartSlice.actions;


`createSlice` also returns a `reducer` function, which will be used while creating the redux store. Here are the complete `cartSlice.ts`.

import { createSlice } from "@reduxjs/toolkit";
import { AppState } from "./store";

// ## CartState Interface
export interface CartState {
    itemsInCart: number;
    totalAmount: number;
}

// ## Define the initial state of Cart State 
const initialState: CartState = {
    itemsInCart: 0,
    totalAmount: 0
};

export const cartSlice = createSlice({
    name: "cart",
    initialState,
    reducers: {
        setItemsInCart(state, action) {
            state.itemsInCart = action.payload;
        }, 
        setTotalAmount(state, action) {
            state.totalAmount = action.payload;
        }
    }
});
export const { setItemsInCart, setTotalAmount } = cartSlice.actions;

export default cartSlice.reducer; 


Creating Store

A store can be considered a collection of different reducer functions like the one we defined above, `createSlice`. It holds the complete state of the application.
 

For this, create a file called `store.ts` inside the `redux` folder.

Import the `configureStore`, `cartSlice`, and `createWrapper` from the respective libraries.

import { configureStore } from  "@reduxjs/toolkit";
import { cartSlice } from  "./cartSlice";
import { createWrapper } from  "next-redux-wrapper";


Using `configureStore` creates the store by passing the `cartSlice` reducers.

const makeStore = () =>
  configureStore({
    reducer: {
      [cartSlice.name]: cartSlice.reducer,
    },
    devTools: true,
  });


You can also pass multiple reducers. Next, you need to pass the makeStore method to the `createWrapper` method and export it.

export type AppStore = ReturnType<typeof makeStore>;
export type AppState = ReturnType<AppStore["getState"]>;
export const wrapper = createWrapper<AppStore>(makeStore);


Here are the complete `store.ts`.

import { configureStore } from "@reduxjs/toolkit";
import { cartSlice } from "./cartSlice";
import { createWrapper } from "next-redux-wrapper";

const makeStore = () =>
  configureStore({
    reducer: {
      [cartSlice.name]: cartSlice.reducer,
    },
    devTools: true,
  });

export type AppStore = ReturnType<typeof makeStore>;
export type AppState = ReturnType<AppStore["getState"]>;
export const wrapper = createWrapper<AppStore>(makeStore);


Connecting App to Store


Go to your `_app.tsx` file and import `wrapper` from `store.ts`.

import { wrapper } from  "../redux/store"


Now instead of importing the App, you wrap it up using the `wrapper` and export.

import '@/styles/globals.css'
import type { AppProps } from 'next/app'
import { wrapper } from "../redux/store"

function App({ Component, pageProps }: AppProps) {
  return <Component {...pageProps} />
}

export default wrapper.withRedux(App)


Getting and Updating Store Values


You already exported a method to update the cart values using `setItemsInCart` and `setTotalAmount`. To get the store values, you need to export another method in `cartSlice.ts`.

export  const  getItemsInCart = (state: AppState) =>  state.cart.itemsInCart;
export  const  getTotalAmount = (state: AppState) =>  state.cart.totalAmount;


The above two methods, `getItemsInCart` and `getTotalAmount`, get the store values. Here is the modified `cartSlice.ts` file.

import { createSlice } from "@reduxjs/toolkit";
import { AppState } from "./store";

// ## CartState Interface
export interface CartState {
    itemsInCart: number;
    totalAmount: number;
}

// ## Define the initial state of Cart State 
const initialState: CartState = {
    itemsInCart: 0,
    totalAmount: 0
};

export const cartSlice = createSlice({
    name: "cart",
    initialState,
    reducers: {
        setItemsInCart(state, action) {
            state.itemsInCart = action.payload;
        }, 
        setTotalAmount(state, action) {
            state.totalAmount = action.payload;
        }
    }
});

export const { setItemsInCart, setTotalAmount } = cartSlice.actions;

export const getItemsInCart = (state: AppState) => state.cart.itemsInCart;
export const getTotalAmount = (state: AppState) => state.cart.totalAmount;

export default cartSlice.reducer; 


Now let's see how you use the Redux store to get and set values. Go to `index.tsx` file and replace the existing code with the following code:

export default function Home() {  const itemsInCart:any = 0;

  const addItemsToCart = () => { 
 }
  
  return (
    <>
      <h2>
        Items in Cart : {itemsInCart}
      </h2>
      <button value="Add" type="button" onClick={addItemsToCart}>
        Add
      </button>
    </>
  )
}


Now if you run your application, you'll be able to see a UI with a counter and a button. So that counter indicates the number of items in the cart, which we'll get from `getItemsInCart`.

To get data from Redux stores, we'll make use of `useSelector` and `getItemsInCart`. Let's import those first,

import { getItemsInCart } from  "@/redux/cartSlice";
import{useSelector}from  "react-redux";


Then you use the above to fetch the store value for `itemsInCart`

const  itemsInCart:any = useSelector(getItemsInCart);


Similarly, to update the store value of `itemsInCart` you need to import  `useDispatch` and `setItemsInCart`.

import { setItemsInCart } from  "@/redux/cartSlice";
import { useDispatch } from  "react-redux";
And on the button click you need to increment the existing value by one.
  const addItemsToCart = () => {
    dispatch(setItemsInCart(parseInt(itemsInCart)+1))
  }


Here is how the modified `index.tsx` file looks:

import { getItemsInCart, setItemsInCart } from "@/redux/cartSlice";
import { useSelector, useDispatch } from "react-redux";

export default function Home() {
  const itemsInCart: any = useSelector(getItemsInCart);
  const dispatch = useDispatch();

  const addItemsToCart = () => {
    dispatch(setItemsInCart(parseInt(itemsInCart) + 1))
  }

  return (
    <>
      <h2>
        Items in Cart : {itemsInCart}
      </h2>
      <button value="Add" type="button" onClick={addItemsToCart}>
        Add
      </button>
    </>
  )
}


Save the above changes and check the application's UI. Initially, it will show the default or initial state value. At the click of a button, the value increments by one.

Wrapping It Up

In this tutorial, you learned about Redux, why we need it in our applications, and how to use and set up Redux in your Next.js application by creating a slice and store.

This is just the tip of the iceberg, and there are many other features in Redux that we can discuss in upcoming tutorials.



Jscrambler

The leader in client-side Web security. With Jscrambler, JavaScript applications become self-defensive and capable of detecting and blocking client-side attacks like Magecart.

View All Articles

Must read next

Web Development

Becoming Familiar with Redux

Today, we’re going to get a taste of how Redux works and take a look at the core concepts of the framework. By the end, we’ll have a low-level counter application.

June 30, 2017 | By Thomas Greco | 5 min read

Web Development

How To Use Redux Persist in React Native with Asyncstorage

The Redux Persist library provides an easy way to save a Redux store in the local storage of React Native apps. In this post, we explore how to set it up.

January 8, 2021 | By Aman Mittal | 15 min read

Section Divider