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 ArticlesMust read next
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
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