Setup Redux in your NextJs Application(with Typescript)

Victor Bruce

Victor Bruce

Tue Oct 25 2022

main

Redux is a global state management javascript library for managing and centralizing an application state.

Goal:

The goal of this blog post is to ensure that by the end, you will be able to add redux to your NextJs application and manage states within your application.

Assumptions:

I assume you have a NextJs application already set up.

Installation:

As of 2022, the Redux team strongly recommends the use of the Redux Toolkit as an approach for writing redux logic. Why? Because Redux Toolkit simplifies most Redux tasks, prevents common mistakes, and makes it easier to write Redux applications.


Install the Redux Toolkit library from either npm or yarn depending on the package manager you prefer or use.

yarn add @reduxjs/toolkit
yarn add react-redux

Or

npm install @reduxjs/toolkit
npm install react-redux

Configuring a Store

import { configureStore } from "@reduxjs/toolkit"

export const store = configureStore({
  reducer: {
    // reference reducers here
  }
})

// create types for state and dispatch
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch

Explanation:

On line 1, we imported a function called configure store that takes in an object as an argument. Here, we pass in the reducers we have created which makes it possible to reference or access other states within other components using the useSelector hook. (we will get to that soon).


On line 3, we create and export a store.

On lines 10 and 11, we create a type for our root state to get better IntelliSense when trying to access the global state.

Define Typed Hooks

Redux Toolkit makes the following hooks available to us for use. These hooks are useSelector and useDispatch. While it is still possible to import the types we defined in our store.ts file above which are RootState and AppDispatch into our components, it is better to create typed versions of the useSelector and useDispatch for our application as well.

Reasons:

  • For useSelector, useAppSelector saves us the need to type our state every time ( state: RootState).
  • For useDispatch, useAppDispatch save us from forgetting to import AppDispatch where it is needed.

import {TypedUseSelectorHook, useDispatch, useSelector} from "react-redux"
import type {RootState, AppDispatch} from "store"

// use throughout your app instead of useDispatch and useSelector

export const useAppDispatch: () => AppDispatch = useDispatch
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector

Create s Slice

import {createSlice, PayloadAction} from "@reduxjs/toolkit"
import {RootState} from "hooks"

type Note = {
  id: string;
  heading: string;
  content: string;
}

interface NoteState {
  notes: Array<Note>
}

const initialState: NoteState = {
  notes: [
    { id: '1', heading: 'Todo for the day', content: '' }
  ]
}

export const noteSlice = createSlice({
  reducers: {
    addNote: (state, action: PayloadAction<Note>) => {
      const note = action.payload;
      state.notes.push(note)
    },
    removeNote: (state, action: PayloadAction<string>) => {
      const id = action.payload;
      const notes = state.notes.filter((note) => note.id !== id);
      state.notes = notes;
    }
  }
})

// actions
export const {addNote, removeNote} = noteSlice.actions 

// selectors
export const selectNotes = (state: RootState) => state.notes.notes

export default noteSlice.reducer

The code above is an example of how to create a slice in Redux. A slice here means, splitting the Redux state into multiple states. A slice is a collection of reducer logic and actions for a single feature in an application. In this example, the feature is to add and delete notes.

Below is a detailed explanation of what is happening in the slice file:

On line 1, we import createSlice and PayloadAction from @reduxjs/toolkit. createSlice is a function that combines the steps of creating an action and a reducer separately. Thus, instead of creating a file to handle action logic and reducer logic by importing createActions and createReducer respectively, we only import createSlice.


On the same line, we are importing PayloadAction. PayloadAction allows us to type the parameters of an action payload.

From lines 3 to 17, we are defining the type and interface of our initial state.

From lines 19 to 31, we create our note slice and export it. In between, we write some reducer logic to add and remove notes.

On line 39, we export our note slice reducer. After exporting we have to go back to our store file to add the reducer to our store.

import { configureStore } from "@reduxjs/toolkit"
import noteReducer from "slice"

export const store = configureStore({
  reducer: {
    notes: noteReducer
  }
})

export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch

Usage:

Here, we will briefly look at how to use the slice we have created in a React component.


import {useAppSelector, useAppDispatch} from "hooks"
import {selectNotes, removeNote} from "slice"

const HomePage = (): JSX.Element => {
  const notes = useAppSelector(selectNotes);
  const dispatch = useAppDispatch();
  
  const deleteNote = (noteId: string) => {
    dispatch(removeNote(noteId))
  }
  
  const renderNotes = notes.map((note) => (
    <div key={note.id}>
      <h1>{note.title}</h1>
      <p>{note.content}</p>
      <button onClick={() => deleteNote(note.id)}>Delete Note</button>
    </div>
  ))
  
  return (
    <div>
      {renderNotes}
    </div>
  )
}

export default HomePage;

On Line 1, we import useAppSelector to access our state and useAppDispatch to dispatch actions and modify our state.

On Line 2, we import a selector(selectNotes) to get the notes state from our slice module and an action (removeNote).

For the rest of the code, we render the list of notes in our state. Each note has a button attached to it which when clicked, will dispatch an action to remove that note from our state.

Summary:

This brings us to the end of this blog post. So far, we have covered:

  • how to set up redux in a NextJs application.
  • how to define typed hooks.
  • how to create a slice.
  • how to use redux in a React component.

Unfortunately, I couldn’t cover topics such as Redux Thunk, and other middleware which will be part of my next blog. Please watch out.

References: