DEV Community

Daniel Guglielmi
Daniel Guglielmi

Posted on

Redux Toolkit with Typescript and Next.js 14

Installing Redux

yarn add @reduxjs/toolkit
yarn add react-redux
Enter fullscreen mode Exit fullscreen mode

Folder Structure

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

Image description

  • 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

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;

Enter fullscreen mode Exit fullscreen mode

Creating the Hooks file

In this file, we will create the hooks to access the useSelector and dispatch.

hooks.ts

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;

Enter fullscreen mode Exit fullscreen mode

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

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'];

Enter fullscreen mode Exit fullscreen mode

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

'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>;
}

Enter fullscreen mode Exit fullscreen mode

Once this file is created, open the app\layout.tsx file to integrate the store provider:
layout.tsx

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>
  );
}

Enter fullscreen mode Exit fullscreen mode

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

import { useAppSelector, useAppDispatch, useAppStore } from '@/lib/hooks';
Enter fullscreen mode Exit fullscreen mode

Create the objects to utilize those hooks:

export default function MyComponent() {

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

.
  return (</div>)
}

Enter fullscreen mode Exit fullscreen mode

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


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;

Enter fullscreen mode Exit fullscreen mode

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

import { updateFloor } from '@/lib/features/bookSlice';
Enter fullscreen mode Exit fullscreen mode

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:

dispatch(updateFloor(5))
Enter fullscreen mode Exit fullscreen mode

References

Redux Toolkit - Typescript Quick Start
Redux Tookit - 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!

Top comments (2)

Collapse
 
alexiscirmi profile image
Alexis Cirmi

Thanks bro

Collapse
 
root9464 profile image
iO

is there a repository?