Redux

Framework Next.js 13 Data Sharing: Redux

Next.js 13 Redux

After Next.js 13, the js components are ALL treated as the server-side component by default.

So if you want to use the client-side function such as createContext, useContext, useState that you have to write 'use client'; on the top of the file to tell the Next.js 13 that this component is the client-side component

1. Install Packages

npm install @reduxjs/toolkit react-redux
npm install --save-dev @redux-devtools/core

yarn add @reduxjs/toolkit react-redux
yarn add @redux-devtools/core --dev

2. Create Redux State Slice

Create redux slice that we want to use on our redux store

// Slice/counterSlice.js
import { createSlice } from '@reduxjs/toolkit'

export const counterSlice = createSlice({
  name: 'counter',
  initialState: {
    value: 0
  },
  reducers: {
    increment: state => {
      // Redux Toolkit allows us to write "mutating" logic in reducers. It
      // doesn't actually mutate the state because it uses the Immer library,
      // which detects changes to a "draft state" and produces a brand new
      // immutable state based off those changes
      state.value += 1
    },
    decrement: state => {
      state.value -= 1
    },
    incrementByAmount: (state, action) => {
      state.value += action.payload
    }
  }
})

// Action creators are generated for each case reducer function
export const { 
  increment, 
  decrement, 
  incrementByAmount 
} = counterSlice.actions

export default counterSlice.reducer

2. Create a Redux Store and import Redux Slice

Import the Redux Slice to the Redux Store and set the counterReducer to the counter reducer variable

// Store/NextGlobalStore.js
import { configureStore } from '@reduxjs/toolkit'
import counterReducer from './Slice/counterSlice'

export default configureStore({
  reducer: {
    counter: counterReducer
  }
})

3. Create Custom Client-Side Store Provider

We want to add the whole Redux Store Provider to the most top-level app/layout.js. But app/layout.js is the server-side component by default and Redux Provider is use for the client-side component.

So we CANNOT import the Redux Provider to the app/layout.js directly. We have to create our custom client-side provider component then import it to the app/layout.js

Write the 'use client'; on the top of the context file to tell the Next.js 13 that this component is the client-side component

// Store/NextGlobalStoreProvider.js
'use client';
// - [Rendering: Server and Client Components | Next.js](https://beta.nextjs.org/docs/rendering/server-and-client-components)
import NextGlobalStore from './NextGlobalStore.js';
import { Provider } from 'react-redux';

export default function NextGlobalStoreProviders({children}) {
  return (
    <Provider store={NextGlobalStore}>
      {children}
    </Provider>
  );
}

4. Add Custom Client-Side Store Provider to the app/layout.js

Import the Custom Redux Provider to the app/layout.js

// app/layout.js
import NextGlobalStoreProvider from '../Store/NextGlobalStoreProvider';

export default async function RootLayout({ children }) {
  return (
    <html>
      {/*
          <head /> will contain the components returned by the nearest parent
          head.js. Find out more at https://beta.nextjs.org/docs/api-reference/file-conventions/head
        */}
      <head />
      <body>
        <NextGlobalStoreProvider>
          <MainLayout>
            {children}
          </MainLayout>
        </NextGlobalStoreProvider>
      </body>
    </html>
  )
}

5. Test Redux On Client-Side Children Component

Write the 'use client'; on the top of the context file to tell the Next.js 13 that this component is the client-side component

Import the redux method to test to manipulate the redux variable.

// app/some-client-side-component.js
"use client";
import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { decrement, increment } from '../Slice/counterSlice'

export function Counter() {
  const count = useSelector(state => state.counter.value)
  const dispatch = useDispatch()

  return (
    <div>
      <div>
        <button
          aria-label="Increment value"
          onClick={() => dispatch(increment())}
        >
          Increment
        </button>
        <span>{count}</span>
        <button
          aria-label="Decrement value"
          onClick={() => dispatch(decrement())}
        >
          Decrement
        </button>
      </div>
    </div>
  )
}

Reference

Tutorial

Debug Tool