🤖

【React】モーダルの開閉をRedux Sliceで状態管理する

2024/11/11に公開

本記事の内容

Reactでモーダルの開閉を状態管理する方法について記載しています。
モーダルの開閉についての実装は次の①、②の方法があります。

  • ① 親コンポーネント/子コンポーネント間でモーダルの開閉フラグをstateを受け渡す方法
  • ② Reduxでモーダルの開閉フラグを状態管理する方法

Reduxを用いた②の方法が断然楽! & Reduxの実装内容について、という内容です。

前提

コンポーネント

図のようなコンポーネントを実装をしているとします。

  • NewMatrixFlowPageコンポーネント
  • MatrixViewコンポーネント
    • MatrixRowコンポーネント
  • ModalforAddFlowstepFormコンポーネント
    • AddFlowstepFormコンポーネント


fig1 : 実装コンポーネント

実装したい機能

NewMatrixFlowPageコンポーネントはページのviewファイルです。
このNewMatrixFlowPage上にMatrixViewコンポーネントを表示しています。

MatrixViewコンポーネントの子コンポーネントであるMatrixRowコンポーネント上の+ボタンを押すと、モーダル(ModalforAddFlowstepForm)が開く機能を実装したいという状況です。

コンポーネントの設計・機能について

このコンポーネントの設計は自分のポートフォリオ「MATRIXFLOW」のものです。
「MATRIXFLOW」についてはこちらをご覧ください
https://qiita.com/maixhashi/items/5da649e290ec13e71465

親/子コンポーネント間でstateの受け渡しはつらい

モーダルを開くだけなのですが、先述のコンポーネントらにモーダルを開くためのフラグ(ModalOpenとします)を受け渡そうとすると...

  • 子コンポーネント → 親コンポーネントへの受け渡し
    • MatrixRowコンポーネント → MatrixViewコンポーネント
    • MatrixViewコンポーネント → NewMatrixFlowPageコンポーネント
  • 親コンポーネント → 子コンポーネントへの受け渡し
    • NewMatrixFlowPageコンポーネント → ModalforAddFlowstepFormコンポーネント

3回の受け渡しが必要になります。しかも、親→子 / 子→親の場合分けが出てきます。
これはつらい。。


fig2 : 親/子コンポーネント間でstateの受け渡し (つらい)

Reduxを使おう

store現る

先ほどの煩雑な親↔︎子の状態の受け渡しを回避したい、そこでReduxです。
Reduxのイメージは、
storeという絶対的親を作って、状態を共有化する」
です。

先ほどの状態の受け渡しは次のようになります。

fig3 : storeを使った場合の仮イメージ図

実際にやり取りされているのは「指令」

fig3はfig2と対比するようにしてモーダル開フラグModalOpenを受け渡ししているように描きましたが、正しくないので「仮イメージ図」としています。
Reduxを使った場合、実際にやり取りするのはモーダル開状態フラグModalOpenではなく、「storeの状態を変更・参照する指令」です。
モーダル開状態フラグを保持しているのはstoreで、storeから動きません。

fig4のようになります。

  • storeの状態を変更
    • storeに状態(state)を定義
    • storeの状態を変更する指令(action)をstoreで定義
    • MatrixRowコンポーネントからactionを実行する(dispatch)
  • storeの状態を参照
    • storeに状態(state)を定義
    • ModalforAddFlowstepFormコンポーネントからstateを参照する指令(useSelector)により、モーダル開フラグを受け取る
    • モーダル開フラグの値( true / false )に基づいてモーダルを表示する/非表示にする


fig4 : storeを使った場合のstateの受け渡し

Redux Sliceの実装内容

Reduxの実装内容について記していきます。

エントリーファイルにstoreの設定を読み込むよう設定

app.jsxのようなエントリーファイルにstoreの設定を読む込むように設定します。

resourses/js/app.jsx
import { createInertiaApp } from '@inertiajs/react';
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
import { createRoot } from 'react-dom/client';

import { Provider } from 'react-redux';
import store from '/path/to/store'; //store.jsへのパスを設定

createInertiaApp({
    setup({ el, App, props }) {
        const root = createRoot(el);

        root.render(
            <>
              <Provider store={store}>
                <App {...props} />
              </Provider>
            </>
        );
    },
});

Sliceファイルの作成

ReduxはState, Reducer, Actionの3要素からなり別々のファイルで記述する方法がありますが、Sliceを使うと1つのファイルにそれらをまとめて記述することが便利です。
https://redux.js.org/tutorials/fundamentals/part-3-state-actions-reducers

resourses/js/store/modalSlice.js
// Sliceをインポート
import { createSlice } from '@reduxjs/toolkit';

// 初期状態を定義
const initialState = {
  ModalOpen: false,
};

// モーダルの開閉状態に関するRedux Sliceを定義
const modalSlice = createSlice({
  // 他のコンポーネントで呼び出す時の名前を定義
  name: 'modal',

  // 初期状態
  initialState,

  // Reducer(Actionを定義する場所)
  reducers: {
    // モーダルを開くAction
    openModal: (state) => {
      state.ModalOpen = true;
    },

    // モーダルを閉じるAction
    closeModal: (state) => {
      state.ModalOpen = false;
    },
  },
});

// 定義したActionを他のコンポーネントで呼び出せるようにexport設定
export const {
  openModal,
  closeModal,
} = modalSlice.actions;

// storeに登録できるようにReducerをexport設定
export default modalSlice.reducer;

Sliceをstoreに登録

storeに定義したSliceファイルを登録します。

resourses/js/store/store.js
import { configureStore } from '@reduxjs/toolkit';
import modalReducer from 'path/to/modalSlice'; //modalSliceへのパスを設定


const store = configureStore({
    reducer: {
        modal: modalReducer,
    },
});

export default store;

Redux Sliceの状態を変更する(Actionをdispatch)

storeで定義されたstate:ModalOpenを変更するアクションをopenModalとして定義したので、モーダルを開くボタンをクリックしたときにそのアクションが実行されるように実装します。
他のコンポーネントからstoreに定義されたアクションを呼び出して使うにはdispatchを使います。

resourses/js/Components/MatrixView.jsx
import React from 'react';

// Reduxのdispatchを使用できるように設定
import { useDispatch } from 'react-redux';

// FontAwesomeのアイコンのインポート
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faSquarePlus } from '@fortawesome/free-solid-svg-icons';

// Store Redux Sliceのインポート
import { openModal } from '/path/to/modalSlice'; //modalSliceへのパスを設定

const MatrixCol = () => {
  // ...省略
};

const MatrixRow = ({ member }) => {
    const dispatch = useDispatch();

    const handleOpenModal = () => {
        dispatch(openModal());
    };

    return (
        <tr>
            <td>
                <div>
                    <div>
                        {member.name}
                    </div>
                </div>
            </td>
            {Array.from({ length: maxFlowNumber }, (_, i) => i + 1).map((flowNumber) => {
                return (
                    <React.Fragment key={flowNumber}>
                        <MatrixCol/>
                    </React.Fragment>
                );
            })}
            <td>
                <button onClick={handleOpenAddFlowstepModal}>
                    <FontAwesomeIcon icon={faSquarePlus} />
                </button>
            </td>
        </tr>
    );
};

const MatrixView = () => {
    return (
      <table>
          <thead>
              <tr>
                  <th>担当者 / フローステップ</th>
                  // ...省略
              </tr>
          </thead>
          <tbody>
          {members.map((member, index) => (
              <MatrixRow/>
          ))}
          // ...省略
    );
};

export default MatrixView;

Redux Sliceの状態を参照する(StateをuseSelectorで参照)

storeの状態を参照するにはuseSelectorを使います。
useSelectoreで参照する状態の値を使ってモーダルの表示を制限する例です。

resourses/js/Components/Pages/NewMatrixFlowPage.jsx
import React from 'react';

// useSelectorを使えるようにインポート設定
import { useSelector } from 'react-redux';

// コンポーネントの読み込み
import ModalforAddFlowstepForm from '/path/to/ModalforAddFlowstepForm';


const NewMatrixFlowPage = () => {
    // useSelectorによりstoreに定義したStateを参照
    const ModalOpen = useSelector((state) => state.modal.ModalOpen);

    return (
      <div>
      //  useSelectorで参照したModalOpenがtrueの時に表示する
          {ModalOpen && (
              <ModalforAddFlowstepForm>
                  <AddFlowstepForm/>
              </ModalforAddFlowstepForm>
          )}
      </div>
    );
};

export default NewMatrixFlowPage;

終わりに

親子間の状態の受け渡しが増えるとコードがカオスになるので早めにRedux使うのが吉と言う所感です

GitHubで編集を提案

Discussion