1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Next.js(AppRouter)でRedux Toolkitの導入

Last updated at Posted at 2024-12-19

Next.jsのApp Routerでの開発で、グローバルステートの管理にRedux Toolkitを導入しようとしましたが、従来のやり方とは少し違ったので、公式ドキュメントを見ながら導入したので、まとめたいと思います。

1. lib/store.tsを追加

公式ドキュメント通り追加します。
libディレクトリに配置するのが推奨されていますが、命名は任意のもので良いそうです

import { configureStore } from '@reduxjs/toolkit';
import contersSlice from './reducers/countersSlice';

export const makeStore = () => {
  return configureStore({
    reducer: {
        // ここに使用するSliceを追加
        counters: contersSlice,
    }
  })
}

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

2. lib/hooks.tsを追加

こちらも公式ドキュメント通り追加します。

import { useDispatch, useSelector, useStore } from 'react-redux'
import type { AppDispatch, AppStore, RootState } from './store'

export const useAppDispatch = useDispatch.withTypes<AppDispatch>()
export const useAppSelector = useSelector.withTypes<RootState>()
export const useAppStore = useStore.withTypes<AppStore>()

3. app/StoreProvider.tsxを追加

こちらも公式ドキュメント通り追加。
use clientを宣言してClient Componentにします。

'use client'
import { useRef } from 'react'
import { Provider } from 'react-redux'
import { makeStore, AppStore } from '../lib/store'

export default function StoreProvider({
  children
}: {
  children: React.ReactNode
}) {
  const storeRef = useRef<AppStore>()
  if (!storeRef.current) {
    storeRef.current = makeStore()
  }

  return <Provider store={storeRef.current}>{children}</Provider>
}

4. app/layout.tsx を修正

ルートレイアウトでを使ってラップします
こうすることで全ルートでstoreを使用することができます

import StoreProvider from './StoreProvider';

// 省略
<StoreProvider>{children}</StoreProvider>

今までは直接ここで<Provider store={store}>みたいな感じで書いていましたが、
AppRouterはServerComponentのため、エラーが出てしまうみたいです。

5. Sliceを追加

数字を増やしたり減らしたりするactionを追加しました。

import { createSlice } from "@reduxjs/toolkit";

export interface CounterType {
  value: number;
}

const initialState: CounterType = {
  value: 0,
};

const countersSlice = createSlice({
  name: "counters",
  initialState,
  reducers: {
    increment: (state) => {
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    },
  },
});

export const { increment, decrement } = countersSlice.actions;
export const countersReducer = countersSlice.reducer;

6. 使ってみる

使用したいコンポーネントで下記のように呼び出したり、値を更新したりできます。
Client Componentでしか使用できないので注意。

'use client';
import React from 'react';
import { useAppSelector, useAppDispatch } from '@/hooks/storeHook';
import { increment, decrement } from '@/states/reducers/countersSlice';

const ExamplePage: React.FC = () => {
  // stateの後のcountersは、lib/store.tsのreducerで設定したkeyです
  const { value } = useAppSelector((state) => state.counters);
  const dispatch = useAppDispatch();

  return (
    <div>
        <button onClick={() => { dispatch(increment()); }}>+1</button>
        <button onClick={() => { dispatch(decrement()); }}>+1</button>
    </div>
  );
};

export default ExamplePage;

懸念していた点

<StoreProvider>はClient Componentなので、
それでラップすると下層は全てClient Componentになるのではないかと心配していましたが、
試しに下層コンポーネントでフックを使おうとするとエラーが出たり
console.logを書いてみてもブラウザのコンソールには出力されませんでしたので、
問題なく下層はデフォルトのServerComponentで動作しているようでした。

詰まったところ

ページ遷移しても値が保持されているかを調べたく

<a href="/b">Bページへ</a>

として移動したところ、値が保持されていなかったので
上手く動作しない!と困惑しましたが、<Link>を使えば解決しました。
初歩すぎて恥ずかしい、、

import Link from 'next/link';

// 省略
<Link href="/b">Bページへ</Link>

参考記事

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?