Redux Toolkit with Typescript and Next.js 14
Published: 2023-12-27 17:12:23

## Installing Redux
```sh
yarn add @reduxjs/toolkit
yarn add react-redux
```

---

## Folder Structure
In the root directory, let's establish the following folder structure:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vizayhwjz1e4hhs6ca6k.png)

- `lib`: This folder will encompass all the Redux files and folders.
- `features`: This section holds the files containing slices that will be integrated into our global store. Here, we can create as many slices as necessary to segment the state per feature.
- `bookSlice.ts`: This file comprises the structure with the state and actions that impact the state.
- `hooks.ts`: Within this file, we will set up new hooks for use with TypeScript in Redux.
- `store.ts`: In this file, we will configure the global store and consolidate the various slices created to make them accessible globally.

---

## Creating the Slice
The following content is an example of the structure; ensure you can adapt this example to your needs.

**bookSlice.ts**
```js
import { createSlice } from '@reduxjs/toolkit';
import type { PayloadAction } from '@reduxjs/toolkit';

interface BookState {
  floor: number | null;
  unit: string;
}

const initialState: BookState = {
  floor: null,
  unit: '',
};

export const bookSlice = createSlice({
  name: 'booking',
  initialState,
  reducers: {
    updateFloor: (state, action: PayloadAction<number>) => {
      state.floor = action.payload;
    },
    updateUnit: (state, action: PayloadAction<string>) => {
      state.unit = action.payload;
    },
  },
});

export const { updateFloor, updateUnit } = bookSlice.actions;
export default bookSlice.reducer;

```
---

## Creating the Hooks file
In this file, we will create the hooks to access the `useSelector` and `dispatch`.

**hooks.ts**
```js
import { useDispatch, useSelector, useStore } from 'react-redux';
import type { TypedUseSelectorHook } from 'react-redux';
import type { RootState, AppDispatch, AppStore } from './store';

export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
export const useAppStore: () => AppStore = useStore;

```
---

## Creating the Store
In the following example, I intentionally added two slices: the previously created `bookSlice.ts` and a new one named `apartmentSlice`. This is to illustrate how to incorporate new slices into the store.

**store.ts**
```js
import { configureStore } from '@reduxjs/toolkit';
import bookSlice from './features/bookSlice';
import apartmentSlice from './features/apartmentSlice';

export const bookStore = () => {
  return configureStore({
    reducer: {
      booking: bookSlice,
      apartment: apartmentSlice,
    },
  });
};

export type AppStore = ReturnType<typeof bookStore>;

export type RootState = ReturnType<AppStore['getState']>;
export type AppDispatch = AppStore['dispatch'];

```
--- 

## Adding the Store Provider to our project
As we are implementing **Redux **in our project with **Next.js 14**, the folder structure we are using is within the `app` directory.

Inside the `app` folder, we will create a file called `StoreProvider.tsx` with the following content:

**StoreProvider.tsx**
```js
'use client';
import { useRef } from 'react';
import { Provider } from 'react-redux';
import { bookStore, AppStore } from '@/lib/store';

export default function StoreProvider({ children }: { children: React.ReactNode }) {
  const storeRef = useRef<AppStore>();
  if (!storeRef.current) {
    // Create the store instance the first time this renders
    storeRef.current = bookStore();
  }
  return <Provider store={storeRef.current}>{children}</Provider>;
}

```

Once this file is created, open the `app\layout.tsx` file to integrate the store provider:
**layout.tsx**
```js
import type { Metadata } from 'next';
import { Inter } from 'next/font/google';
import StoreProvider from './StoreProvider';
import './globals.css';

const inter = Inter({ subsets: ['latin'] });

export const metadata: Metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
};

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang='en'>
      <body className={inter.className}>
        <StoreProvider>{children}</StoreProvider>
      </body>
    </html>
  );
}

```

We have modified the content within the `<body>` by encapsulating the `{children}` with `<StoreProvider></StoreProvider>`, as evident in the code snippet above.

--- 

## Accessing to the Store from our components
### Import the hooks
```js
import { useAppSelector, useAppDispatch, useAppStore } from '@/lib/hooks';
```

Create the objects to utilize those hooks:
```js
export default function MyComponent() {

  const store = useAppStore();
  const floor= useAppSelector((state) => state.booking.floor);
  const dispatch = useAppDispatch();

….
  return (</div>)
}

```

When utilizing the `useAppSelector` hook and specifying `state.<nameOfTheSlice>.<field>`, we gain access to that specific field. In the example above, you can observe that we are accessing the `state.booking.floor`. '**booking**' is the name assigned in the store (_booking: bookSlice_), and **floor** is the field found within the **initialState**, which contains the structure of the slice.

---

## Updating the State
To update the state, we need to utilize the **dispatch** function to execute our **actions**.

Let's examine the `bookSlice.ts` file to review where we place our **actions**:

**bookSlice.ts**
```js
…
export const bookSlice = createSlice({
  name: SupabaseTableName.BOOKING,
  initialState,
  reducers: {
    updateFloor:  (state, action: PayloadAction<number>) => {
      state.floor = action.payload;
    },
    updateUnit: (state, action: PayloadAction<string>) => {
      state.unit = action.payload;
    },
  },
});

export const { updateFloor, updateUnit } = bookSlice.actions;
export default bookSlice.reducer;

```

As you can see, we have **updateFloor **and **updateUnit** as the functions inside the `reducers` object. Therefore, to update our state, we need to make use of these functions.

To do so, let's go back to our React component, as that is where we need to implement these changes.

**MyComponent.tsx**
Import the function that we want to use
```js
import { updateFloor } from '@/lib/features/bookSlice';
``` 

The usage of **updateFloor** is **updateFloor(5)** since the `PayloadAction<number>` indicates that it will receive a number. Therefore, we send the number **5** to update the `floor` field.

---

### How to use it
In our `MyComponent.tsx` file, we can create a function to execute this action. So, when you need to update this state, you have to pass the reducer function to the **dispatch** function to affect the store in this manner:
```js
dispatch(updateFloor(5))
```

---
## References
[Redux Toolkit - Typescript Quick Start](https://redux-toolkit.js.org/tutorials/typescript)
[Redux Tookit - Usage with Typescript](https://redux-toolkit.js.org/usage/usage-with-typescript)

---

## Conclusion
In this brief document, we explored the configuration of Redux Toolkit State Management using Typescript in Next.js version 14. We included only some basic operations to avoid making things more complicated, as the purpose of this document is to share the foundational structure required for setting up our configuration to use Redux.

I hope you find this information useful. If you come across any errors or wish to contribute with comments to enhance this document, I would be more than happy to review your feedback.

Happy coding!