Redux Toolkit with Typescript and Next.js 14
Daniel Guglielmi
Posted on December 27, 2023
Installing Redux
yarn add @reduxjs/toolkit
yarn add react-redux
Folder Structure
In the root directory, let's establish the following folder structure:
-
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;
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;
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'];
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>;
}
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>
);
}
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';
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>)
}
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;
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';
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))
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!
Posted on December 27, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.