State Management in Next.js with Redux Toolkit
Tags: react, redux, nextjs, reduxtoolkit
Published: 2023-01-03 00:22:32

by [Divine Orji](https://blog.openreplay.com/authors/divine-orji)



State management is an essential concept in modern web application development. It is how an application's components load, store, and share data.

Redux is a library that helps developers keep track of changes in a complex application. It uses "actions" and "reducers" to update the state of your app and a "store" to save the current state. This makes it easier to build and maintain large apps and to fix any problems that come up.

Redux Toolkit is a collection of pre-built tools, functions, and structures that make it easier to work with Redux. You can use these tools and solutions as a starting point and customize them to fit your needs. Its features include automatic immutable updates and a streamlined way to define reducers. It helps you write code that's efficient and easy to update.

Next Redux Wrapper is a library that makes it easier to use Redux with a Next.js app. It provides tools and helpers that ensure your Redux store works well with Next.js's server-side rendering.

In this article, you will learn how to manage the state in your Next.js application using Redux Toolkit and Next Redux Wrapper. You will implement `react-redux`, `@reduxjs/toolkit`, and `next-redux-wrapper` to update the state of an app's comment section. This is how your app will look like:

![--](https://blog.openreplay.com/images/state-management-in-next-js-with-redux-toolkit/images/sONvw8y.gif)

The complete demo is available on [CodeSandbox](https://codesandbox.io/p/github/dpkreativ/picomment/main?file=%2Fpages%2Findex.js), and its source code is on [GitHub](https://github.com/dpkreativ/picomment).

## Generate a repo from the starter template

Click [here](https://github.com/dpkreativ/nextjs-picomment-starter/generate) to generate the starter files for this project based on an existing template repo:

![--](https://blog.openreplay.com/images/state-management-in-next-js-with-redux-toolkit/images/whXWLoY.png)

After successfully generating the repo, copy its Git URL:

![--](https://blog.openreplay.com/images/state-management-in-next-js-with-redux-toolkit/images/PjIqop2.png)

In your PC's terminal, navigate into your preferred directory and run the command below to clone the project:

```bash
git clone <repo-git-url>
```

After successfully cloning the project, install dependencies by running the command below in your project's terminal:

```bash
yarn

# or npm install. But for this demo, yarn is preferred.
```

On completion, run `yarn dev` in your project's terminal and navigate to `[localhost:3000](http://localhost:3000)` on your preferred browser to see the starter UI:

![--](https://blog.openreplay.com/images/state-management-in-next-js-with-redux-toolkit/images/9Nns038.png)



## Create a Redux store

Run the command below in your project's terminal to install these three packages:

```bash
yarn add @reduxjs/toolkit react-redux next-redux-wrapper
```

After successfully installing the required packages, open the project in your preferred code editor and create a `store` folder with a `store.js` file.

Update this file with the code below:

```javascript
import { configureStore } from '@reduxjs/toolkit';
import { createWrapper } from 'next-redux-wrapper';

const makeStore = () =>
  configureStore({
    reducer: {},
    devTools: true,
  });

export const wrapper = createWrapper(makeStore);
```

Here, you did the following:

- Imported `configureStore` from `@reduxjs/toolkit` and `createWrapper` from `next-redux-wrapper`
- Defined a `makeStore` function that uses `configureStore` to create a new Redux store. The `reducer` object will contain any reducer functions we create for this project
- Exported a `wrapper` object, which contains an instance of `createWrapper` that takes `makeStore` as its parameter. This `wrapper` object will be used to integrate the Redux store with your Next.js application.

When your user loads the app for the first time, the `wrapper` creates a server-side store using `makeStore`, which contains the state of your app. As your user updates the app, the state changes first on the server side, and `next-redux-wrapper` pushes those updates to the client side.

<h2>Open Source Session Replay</h2> 

_<a href="https://github.com/openreplay/openreplay" target="_blank">OpenReplay</a> is an open-source, session replay suite that lets you see what users do on your web app, helping you troubleshoot issues faster. OpenReplay is self-hosted for full control over your data._

![OpenReplay](https://blog.openreplay.com/banner-blog_1oYPGT.png)

_Start enjoying your debugging experience - <a href="https://github.com/openreplay/openreplay" >start using OpenReplay for free</a>._



## Implement Redux store

In your project, navigate to the `pages/_app.js` file and update it with the code below:

```javascript
import { Provider } from 'react-redux';
import { wrapper } from '../store/store';
import '../styles/globals.css';

function MyApp({ Component, ...rest }) {
  const { store, props } = wrapper.useWrappedStore(rest);
  const { pageProps } = props;
  return (
    <Provider store={store}>
      <Component {...pageProps} />
    </Provider>
  );
}

export default MyApp;
```

Here, your `MyApp` function returns a `<Component />` nested in a `<Provider>` from `react-redux`.

This `<Provider>` holds your redux `store`, obtained from the `wrapper` you created earlier using the `useWrappedStore` hook.

## Setting Up Redux Toolkit Slices

According to the documentation, a slice is a collection of Redux reducer logic and actions for a single feature in your app, typically defined together in a single file.

In Redux Toolkit, the `createSlice` function takes in three parameters:

- The name of your slice
- Its initial state
- A `reducers` object containing all the reducers you want to perform on that slice.

It then automatically generates an `action` to match each of the slice's reducers, eliminating the need to manually create actions for each reducer.

In your `store` folder, create a `slices` folder that contains a `commentSlice.js` file, and update it with the code below:

```javascript
import { createSlice } from '@reduxjs/toolkit';
import { HYDRATE } from 'next-redux-wrapper';

const initialState = {
  value: [
    {
      comment: 'Gojo looks nice. Excellent work amigo!',
      username: 'Saitama',
    },
    {
      comment: 'Catoru Sensei! Konnichiwa!',
      username: 'Yuji',
    },
  ],
};

export const commentSlice = createSlice({
  name: 'comments',
  initialState,
  reducers: {
    // Action to add comment
    addComment: (state, action) => {
      state.value = [...state.value, action.payload];
    },

    // Special reducer for hydrating the state
    extraReducers: {
      [HYDRATE]: (state, action) => {
        return {
          ...state,
          ...action.payload.comments,
        };
      },
    },
  },
});

export const { addComment } = commentSlice.actions;
export const selectComments = (state) => state.comments.value;
export default commentSlice.reducer;
```

Here, you set the initial state of your comments, created a `commentSlice`, and set an `addComment` reducer.

You also used the `HYDRATE` function in `next-redux-wrapper` to ensure that the state on the server side matches the client side of your app.

You then exported your `addComment` action, `selectComments`, which contains your state, and your `commentSlice.reducer` by default.

## Set up Authentication Slice

Similar to the Comment Slice you created earlier, the Authentication Slice will contain your app's initial authentication state, some reducers to update the state, and extra reducers to hydrate it using `next-redux-wrapper`.

In your `slices` folder, create an `authSlice.js` file and update it with the code below:

```javascript
import { createSlice } from '@reduxjs/toolkit';
import { HYDRATE } from 'next-redux-wrapper';

const initialState = {
  authState: false,
  authUser: ",
};

export const authSlice = createSlice({
  name: 'auth',
  initialState,
  reducers: {
    setAuthState(state, action) {
      state.authState = action.payload;
    },
    setAuthUser(state, action) {
      state.authUser = action.payload;
    },

    extraReducers: {
      [HYDRATE]: (state, action) => {
        return {
          ...state,
          ...action.payload.auth,
        };
      },
    },
  },
});

export const { setAuthState, setAuthUser } = authSlice.actions;
export const selectAuthState = (state) => state.auth.authState;
export const selectAuthUser = (state) => state.auth.authUser;
export default authSlice.reducer;
```

Here, your initial `authState` is `false` to indicate that the user has not logged in, and `authUser` is an empty string.

You then exported `setAuthState` and `setAuthUser` as actions, `selectAuthState` and `selectAuthUser` to hold authentication data, and `authSlice.reducer` by default.

## Update Redux Store with Slices

In your `store` folder, update `store.js` with the code below:

```javascript
import { configureStore } from '@reduxjs/toolkit';
import { createWrapper } from 'next-redux-wrapper';
import { authSlice } from './slices/authSlice';
import { commentSlice } from './slices/commentSlice';

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

export const wrapper = createWrapper(makeStore);
```

You imported the slices to your main `store` and set their reducers in `configureStore()`.

## Updating State with useSelector and useDispatch

React-Redux provides two useful hooks for handling state in functional components: `useSelector()` and `useDispatch()`.

The `useSelector()` hook lets a React component access data from a Redux store. It takes a selector function and returns its current value.

On the other hand, `useDispatch()` provides access to the Redux `dispatch` function. This allows a React component to dispatch actions to the Redux store. It is typically used to update the store in response to user interactions or other events in the component.

## Set Up Authentication

In this app, users need to create a username before adding comments.

In your code editor, open `Login.js` located at `components/ui/organisms/` and update it with the code below:

```javascript
import { useDispatch } from 'react-redux';
import Button from '../atoms/Button';
import Input from '../atoms/Input';

export default function Login() {
  const dispatch = useDispatch();

  function handleLogin(e) {
    e.preventDefault();
    dispatch(setAuthState(true));
    dispatch(setAuthUser(e.target.user.value));
  }

  return (
    <form className="flex space-x-2" onSubmit={handleLogin}>
      <div className="input-box">
        <Input id={`user`} type={`text`} placeholder={`Set username`} />
      </div>
      <Button type={`submit`}>login</Button>
    </form>
  );
}
```

In the code above, you updated the state of your authentication slice with `useDispatch()`, setting it to `true` and saving the user's name too.

Navigate to `pages/index.js` and update it with the code below:

```javascript
import Head from 'next/head';
import HomeLayout from '../components/layouts/HomeLayout';
import Comment from '../components/ui/molecules/Comment';
import Hero from '../components/ui/organisms/Hero';
import NewComment from '../components/ui/organisms/NewComment';
import Login from '../components/ui/organisms/Login';
import { Comments } from '../components/assets/Comments';
import { useSelector } from 'react-redux'; // updated
import { selectAuthState } from '../store/slices/authSlice'; //updated

export default function Home() {
  const authState = useSelector(selectAuthState); // updated
  const comments = Comments.value;

  return (
    <div>
      <Head>
        <title>Picomment</title>
        <meta name= "description" content= "Drop comments on cute cat pictures"/>
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <HomeLayout>
        {/* Hero Section */}
        <Hero
          imageSrc={`/images/cute-cat.jpg`}
          imageAlt={`Gojo Cat`}
          imageTitle={`Catoru Gojo`}
        />

        {/* Comment Section */}
        <section className="py-5">
          {comments.map((obj, idx) => (
            <Comment key={idx} comment={obj.comment} username={obj.username} />
          ))}
        </section>

        {/* Input Section - updated */}
        {authState ? <NewComment /> : <Login />}
      </HomeLayout>
    </div>
  );
}
```

Here, you implemented `useSelector`, which takes `selectAuthState` from `authSlice.js` as its parameter.

The value of your `useSelector` is stored in an `authState` variable and conditionally displays a `<NewComment />` when it's true or `<Login />` when it's false.

## Add New Comments

Open `NewComment.js` located at `components/ui/organisms/` and update it with the code below:

```javascript
import { useDispatch, useSelector } from 'react-redux'; //updated
import { selectAuthUser } from '../../../store/slices/authSlice'; // updated
import { addComment } from '../../../store/slices/commentSlice'; // updated
import Avatar from '../atoms/Avatar';
import Button from '../atoms/Button';
import Input from '../atoms/Input';

export default function NewComment() {
  const dispatch = useDispatch(); // updated
  const user = useSelector(selectAuthUser); // updated

  // Updated function
  function handleNewComment(e) {
    e.preventDefault();
    dispatch(
      addComment({
        comment: e.target.comment.value,
        username: user,
      })
    );
    e.target.comment.value = '';
  }

  return (
    <section>
      <form
        className="flex space-x-2 items-center h-full"
        onSubmit={handleNewComment}
      >
        <Avatar />
        <div className="input-box">
          <Input
            id={`comment`}
            type={`text`}
            placeholder={`${user}'s comment`}
          />
        </div>
        <Button type={`submit`}>send</Button>
      </form>
      <div className="py-2">
        {/* Logout button - updated */}
        <Button onClick={() => dispatch(setAuthState(false))}>logout</Button>
      </div>
    </section>
  );
}
```

Here, you imported `useSelector` and `useDispatch` from React-Redux and got the current name of the user with `selectAuthUser`.

You then updated the `handleNewComment` function to add a comment with the user's name.

The logout button sets authentication to `false`, which disables the comment input and displays the login input.

## Display Comments

In your `pages/index.js`, update the `comments` variable in your `Home` function with the code below:

```javascript
const comments = useSelector(selectComments);
```

Also, remember to import `selectComments` from your `commentSlice.js` file:

```javascript
import { selectComments } from '../store/slices/commentSlice';
```

In your browser, open `[localhost:3000](http://localhost:3000)` to view the app and interact with it:

![--](https://blog.openreplay.com/images/state-management-in-next-js-with-redux-toolkit/images/sONvw8y.gif)

## Conclusion

In this article, you learned how to implement Redux Toolkit and Next Redux Wrapper in a Next.js project.

Redux Toolkit provides a simple and powerful toolset for working with Redux, and **`next-redux-wrapper`** makes it easy to integrate Redux with Next.js. Together, these libraries provide a powerful and flexible solution for managing the state in a Next.js application.

> A TIP FROM THE EDITOR: For more on Redux Toolkit, don't miss reading [Building a shopping cart in React with Redux Toolkit and Redux Persist](https://blog.openreplay.com/building-a-shopping-cart-in-react-with-redux-toolkit-and-redux-persist).






[![newsletter](https://blog.openreplay.com/newsletter_Z1P0UIP.png)](https://newsletter.openreplay.com/)