ReactのReduxの基礎は理解できていると思うのでより実践的なコードでReduxの理解を深めたいという人向けにTodoリストのアプリケーションを作成を通してReduxの説明を行っています。useState, useSelector, useDispatchなどのReact Hooksを利用して作成しておりconnectやmapStateToPropsは利用していません。Reduxのみで設定した後にRedux Toolkitでの設定方法も説明しています。

作成するTodoリストのアプリではTodoリストの追加、削除、Todoリストのステータス変更(未完了→完了)の機能を持ちます。

Reduxの基礎を学習したい場合には以下の公開済み文書を参考にしてください。

環境の準備

Reduxを利用してTodoリストのアプリケーションを作成する前に動作確認を行う環境を構築するためにViteを利用してReactプロジェクトの作成を行います。”npm create vite@latext”コマンドを実行するとプロジェクト名、フレームワーク、Variantを聞かれるので”redux-todo-list”, “React”, “JavaScript”を選択します。プロジェクト名は任意の名前をつけてください。


% npm create vite@latest

> npx
> create-vite

✔ Project name: … redux-todo-list
✔ Select a framework: › React
✔ Select a variant: › JavaScript

Scaffolding project in /Users/mac/Desktop/redux-learn...

Done. Now run:

  cd redux-learn
  npm install
本文書ではnpmコマンドを利用していますがyarn, pnpmコマンドを使うことも可能です。
fukidashi

作成したプロジェクトディレクトリに移動して、”npm install”コマンドを実行してJavaScriptライブラリのインストールを行い、npm run devコマンドで開発サーバの起動を行います。


 % cd redux-todo-list 
 % npm install
 % npm run dev

> redux-todo-list@0.0.0 dev
> vite
  VITE v5.3.4  ready in 724 ms

  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose
  ➜  press h + enter to show help

http://localhost:5173にブラウザからアクセスすると下記の初期画面が表示されます。

Vite+Reactのトップページ
Vite+Reactのトップページ

デフォルトで設定されているスタイルを解除するためにsrcディレクトリのmain.jsxファイルを開いてindex.cssのimportをコメントします。


import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
// import './index.css'

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
)

srcディレクトリのApp.jsxファイルを以下のコードに更新します。


function App() {
  return (
    <div>
      <h1>ReduxでTodoリスト作成</h1>
    </div>
  );
}

export default App;

ブラウザで確認するとApp.jsxに記述した”ReduxでTodoリスト作成”が表示されます。

タイトルの表示
タイトルの表示

Reactでの準備は完了したのでreduxとreact-reduxをインストールします。

reduxとreact-reduxのインストール

ReactでReduxを利用するためにはreduxとreact-reduxをインストールする必要があります。npmコマンドでインストールを行います。


 % npm install redux react-redux

ライブラリをインストール後はpackage.jsonファイルでインストールしたライブラリとバージョンを確認しておきます。


{
  "name": "redux-todo-list",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
    "preview": "vite preview"
  },
  "dependencies": {
    "react": "^18.3.1",
    "react-dom": "^18.3.1",
    "react-redux": "^9.1.2",
    "redux": "^5.0.1"
  },
  "devDependencies": {
    "@types/react": "^18.3.3",
    "@types/react-dom": "^18.3.0",
    "@vitejs/plugin-react": "^4.3.1",
    "eslint": "^8.57.0",
    "eslint-plugin-react": "^7.34.3",
    "eslint-plugin-react-hooks": "^4.6.2",
    "eslint-plugin-react-refresh": "^0.4.7",
    "vite": "^5.3.4"
  }
}

Storeの作成

Todoリストを保存するためのStoreの作成を行います。srcディレクトリの下にstoreディレクトリを作成し、その下にindex.jsファイルを作成します。


import { createStore } from "redux";

const store = createStore(reducer);

export default store;

createStoreの引数にはreducerが必須なのでreducerを作成します。

reducerの作成

Todoリストオブジェクトはnameとcompleteプロパティを持ち、初期値には2つのTodoリストを保持させます。nameプロパティはTodoの名前、completeプロパティはそのTodoが完了しているか未完了かを識別するために利用します。reducerのactionはまだ何も設定を行っていません。


import { createStore } from "redux";

const initialState = {
  lists: [
    {
      name: "ブログを確認",
      complete: false,
    },
    {
      name: "メールの返信",
      complete: false,
    },
  ],
};

const reducer = (state = initialState, action) => {
  return state;
};

const store = createStore(reducer);

export default store;

Providerの設定

srcディレクトリのmain.jsxファイルを開いてProviderとstoreの設定を行います。Providerの設定が完了するとReactのコンポーネントからReduxのStoreにアクセスする準備は完了です。


import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.jsx';
import { Provider } from 'react-redux';
import store from './store/index.js';
// import './index.css'

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>
);

Todoリストへのアクセスと表示

Reactコンポーネントからlistsにアクセスするための準備が整ったのでStoreに保管されているlistsをブラウザに表示します。表示はApp.jsのコンポーネントで行います。

Storeに保管されているlistsデータにはuseSelector Hookを利用してアクセスします。コンポーネントの先頭ではimportも忘れずに行います。


import { useSelector } from "react-redux";
//略
const lists = useSelector((state) => state.lists);

useSelectorを利用してlistsの値を取得できたのでmap関数で展開し、ブラウザ上に表示させます。


import { useSelector } from 'react-redux';

function App() {
  const lists = useSelector((state) => state.lists);
  return (
    <div>
      <h1>ReduxでTodoリスト作成</h1>
      <h2>Todoリスト</h2>
      <ul>
        {lists.map((list, index) => (
          <li key={index}>{list.name}</li>
        ))}
      </ul>
    </div>
  );
}

export default App;

ブラウザで確認すると初期値で設定した2つのTodoリストが表示されます。ReduxのStoreに保管されたデータへのアクセスと表示をここまでの処理で実現することができました。

ToDoリストを表示
ToDoリストを表示

完了リストと未完了リスト

各リストにはcompleteプロパティがあり、リストが完了しているか完了していないかを識別することができるのでTodoリストを2つのリストに分けます。

filter関数を利用することで完了していないリストを取り出し、取り出したリストでmapを実行してリストを展開しています。completeがfalseであるリストのみを表示させることができます。


{lists
  .filter((list) => list.complete === false)
  .map((list, index) => (
    <div key={index}>{list.name}</div>
  ))}

完了のリストはlist.complete === trueに設定します。completeがtrueのリストのみ表示させます。


{lists
  .filter((list) => list.complete === true)
  .map((list, index) => (
    <div key={index}>{list.name}</div>
  ))}

完了と未完了のコードをApp.jsxの中に組み込みます。


import { useSelector } from "react-redux";

function App() {
  const lists = useSelector((state) => state.lists);
  return (
    <div className="App">
      <h1>ReduxでTodoリスト作成</h1>
      <h2>未完了のTodoリスト</h2>
      <ul>
        {lists
          .filter((list) => list.complete === false)
          .map((list, index) => (
            <div key={index}>{list.name}</div>
          ))}
      </ul>
      <h2>完了したTodoリスト</h2>
      <ul>
        {lists
          .filter((list) => list.complete === true)
          .map((list, index) => (
            <div key={index}>{list.name}</div>
          ))}
      </ul>
    </div>
  );
}

export default App;

初期値のcompleteの値がすべてfalseなので未完了のTodoリストに2つのリストが表示されます。

Todoリストを2つに分ける
Todoリストを2つに分ける

reducerのACTIONの設定

ここからはreducerを利用してStoreに保管されているlistsに対して変更を加えています。変更を加えるためにはACTIONとreducerでのACTIONの処理の実装が必要となります。

リストの移動(未完了→完了)

未完了のTodoリストが完了したら完了リストに移動できるように完了ボタンを追加します。onClickイベントを設定して、ボタンをクリックするとdoneListメソッドを実行し、どのリストか識別できるようにlist.nameを引数とします。


 <button onClick={() => doneList(list.name)}>完了</button>

Todoリストの横にボタン要素を追加します。


  <ul>
    {lists
      .filter((list) => list.complete === false)
      .map((list, index) => (
        <div key={index}>
          {list.name}
          <button onClick={() => doneList(list.name)}>完了</button>
        </div>
      ))}
  </ul>

Storeに保管されているデータを変更する必要があるのでdispatch関数を利用してreducerにACTIONを通知します。dispatch関数の引数にはACTIONを設定する必要があります。reducer側でACTIONの設定はまだ行っていませんが、ACTIONのtypeを”DONE_LIST”としてpayloadにはdoneListメソッドから渡されるnameを設定します。nameはリストの中から対象のTodoを特定する際に利用します。


import { useSelector, useDispatch } from "react-redux";
//略
const dispatch = useDispatch();
const doneList = (name) => {
  dispatch({ type: "DONE_LIST", payload: name });
};

reducerで”DONE_LIST”のACTIONを設定します。switchを使ってaction.typeの値により変更処理を分岐させています。今回のTodoリストのアプリケーションでは3つのACTIONの処理をreducerに追加することになります。

”DONE_LIST”の処理では現在のstate.listsをmap関数で展開し、payloadのnameと異なるリストはそのまま戻し、payloadのnameと一致するリスト名を持つリストを取り出してcompleteプロパティの値をtrueに設定して新しいstateとして戻しています。reducerではstateそのものを変更するのではなく現在のstateを元に新しいstateを作成する必要があるため下記のようなコードとなります。

下記のコードではその意味がわかりにくかもしれませんが、最後に説明を行う”ADD_LIST”の処理のコードを確認することでその意味を理解することができます。
fukidashi

const reducer = (state = initialState, action) => {
  switch (action.type) {
    case 'DONE_LIST':
      return {
        lists: state.lists.map((list) => {
          if (list.name !== action.payload) return list;
          return {
            ...list,
            complete: true,
          };
        }),
      };
    default:
      return state;
  }
};

ブラウザで確認すると完了ボタンがついており、完了ボタンをクリックすると未完了リストから完了リストにリストが移動します。

完了ボタンを押す前
完了ボタンを押す前

ブログの確認の右にある完了ボタンを押すと完了した Todoリストに移動します。

リストの移動
リストの移動

Storeに保管されているデータの変更の流れを再度確認します。完了ボタンを押すとdoneList関数が実行され、dispatch関数でreducerにACTIONの”DONE_LIST”を実行するように通知を行います。”DONE_LIST”のACTIONを受け取ったreducerがpayloadを利用してTodoリストのリスト情報を変更します。変更はリアルタイムでブラウザ上の描写に反映されます。

ここまで設定が理解できれば自分の力でTodoリストのアプリケーションの機能を追加したり変更したりすることが可能です。その理由は先ほど追加した方法と同じ方法で各機能の関数とreducer内でのACTIONの処理を追加していくだけだからです。実際にリストの削除などの機能を追加していきましょう。

リストの削除

削除ボタンを追加してdeleteList関数を作成します。


<button onClick={() => deleteList(list.name)}>削除</button>

//略
<ul>
  {lists
    .filter((list) => list.complete === false)
    .map((list, index) => (
      <div key={index}>
        {list.name}
        <button onClick={() => doneList(list.name)}>完了</button>
        <button onClick={() => deleteList(list.name)}>削除</button>
      </div>
    ))}
</ul>
//略

deleteListメソッド内ではdispatch関数の引数にtypeがDELETE_LISTを持つACTIONを設定します。削除するリストを識別するためにpayloadにはnameを設定します。


const deleteList = (name) => {
  dispatch({ type: 'DELETE_LIST', payload: name });
};

ACTIONのtypeがDELETE_LISTなのでreducerにDELETE_LISTの処理を追加します。

state.listsからpayloadで受け取ったnameを持つリストを削除するのではなく、現在のstate.listsの中からfilterを使ってpayloadのnameを持たないリストのみを取り出して新しいstateにしています。ここでも既存のstateを更新するのではなく既存のstateから新しいstateを作成しています。


const reducer = (state = initialState, action) => {
  switch (action.type) {
    case 'DONE_LIST':
      return {
        lists: state.lists.map((list) => {
          if (list.name !== action.payload) return list;
          return {
            ...list,
            complete: true,
          };
        }),
      };
    case 'DELETE_LIST':
      return {
        lists: state.lists.filter((list) => list.name !== action.payload),
      };

    default:
      return state;
  }
};

ブラウザで確認するとリストの横に完了ボタンと削除ボタンが表示され、追加した削除ボタンをクリックするとリストはブラウザ上から消えます。

削除ボタンの表示
削除ボタンの表示

削除ボタンをクリックするとリストから削除されます。

削除ボタンをクリックしリストから削除
削除ボタンをクリックしリストから削除

リストの追加

最後にリストへの追加機能を設定します。入力した値を保持するためにReact Hooksの一つであるuseStateを利用します。まずuseStateを利用するためimportを行います。useStateを利用することでFunctionコンポーネントにデータ(変数)を保持することができます。


import { useState } from "react";
import { useDispatch, useSelector } from 'react-redux';
//略

ここではTodoリストのプロパティであるnameとcompleteをuseStateを使って変数として宣言し入力フォームで入力した値を保持します。name, completeが変数でsetName、senCompleteを利用して変数を変更します。useStateの引数には初期値を設定することができ、nameの初期値は””、completeは入力時にはTodoは未完了なのでfalseに設定しています。


//略
function App() {
  const lists = useSelector((state) => state.lists);
  const dispatch = useDispatch();

  const [name, setName] = useState('');
  const [complete, setComplete] = useState(false);
//略

input要素を追加しvalueに変数nameを設定し、入力を行うとonChangeイベントによりinputTextメソッドが実行されます。


//略
<h1>ReduxでTodoリスト作成</h1>
<input type="text" value={name} onChange={inputText} />
//略

inputTextメソッドではinput要素で入力された値をe.target.valueから取得しsetNameの引数に設定しnameに入力値を反映されます。


//略
const [name, setName] = useState('');
const [complete, setComplete] = useState(false);

const inputText = (e) => {
  setName(e.target.value);
};
//略

nameの値をTodoリストに追加するための機能を実装するため新たに”追加”ボタンを追加します。”追加”ボタンをクリックするとclickイベントによりaddListメソッドが実行されます。addListメソッドの中にdispatch関数を記述し、追加に関連する新規のACTIONを設定することでreducerに通知します。


//略
<h1>ReduxでTodoリスト作成</h1>
<input type="text" value={name} onChange={inputText} />
<button onClick={addList}>追加</button>
//略

もしnameに値がない場合はdispatchは行いません。またここでもsetCompleteで追加するTodoリストを未完了に設定しています。Actionのtypeを”ADD_LIST”にしています。このtypeの処理はreducerに未登録なので後ほど設定を行います。payloadにはオブジェクトでnameとcompoleteの値を入れます。nameには入力フォームで入力した値、completeはfalseが入っています。


//略
const inputText = (e) => {
  setName(e.target.value);
};

const addList = () => {
  if (!name) return;

  setComplete(false);

  dispatch({
    type: 'ADD_LIST',
    payload: {
      name,
      complete,
    },
  });
  setName('');
};
//略

type”ADD_LIST”のACTIONの処理をreducerに追加します。

reducerでは現在のstateに対してACTION内に記述されている処理を適用し、新しいstateを作成します。現在のstateを直接更新しません。ADD_LISTのコードが新しいstateと現在のstateを更新するという意味の違いを理解するのが一番簡単です。

下記が新しいstateをreturnしている処理です。分割代入により…state.listsにlistsを展開し、action.payloadに入った新しいリストを加えて、新しいlistsの配列を作成してオブジェクトでreturnしています。


const reducer = (state = initialState, action) => {
  switch (action.type) {
    case 'ADD_LIST':
      return {
        lists: [...state.lists, action.payload],
      };
    case 'DONE_LIST':
      return {
        lists: state.lists.map((list) => {
          if (list.name !== action.payload) return list;
          return {
            ...list,
            complete: true,
          };
        }),
      };
    case 'DELETE_LIST':
      return {
        lists: state.lists.filter((list) => list.name !== action.payload),
      };

    default:
      return state;
  }
};

もしstateを更新する場合は既存のlistsの配列に新たに配列の要素を追加するのでpushにより追加を行うことができます。しかしreducerでは以下の処理を記述していはいけません。


state.lists.push(action.payload)

動作確認

予定していた機能の実装が完了したので動作確認を行います。

入力フォームに新たなTodoである”Zoomの会議”を入力して追加ボタンをクリックします。クリックすると未完了のTodoリストの一番下に追加されます。

Zoomの会議
Zoomの会議をTodoに追加

“メールの返信”が完了したので完了ボタンをクリックすると完了したTodoリストへ移動します。

完了したTodoリストの完了ボタンをクリック
完了したTodoリストの完了ボタンをクリック

“ブログの確認”が必要なくなったので削除ボタンを教えてリストから削除します。

必要なくなったTodoの削除
必要なくなったTodoの削除

Reduxを利用して設定したTodoリストの追加・更新・削除を行うことができました。

作成したコード

作成したstore/index.jsファイルは下記の通りです。


import { createStore } from 'redux';

const initialState = {
  lists: [
    {
      name: 'ブログを確認',
      complete: false,
    },
    {
      name: 'メールの返信',
      complete: false,
    },
  ],
};

const reducer = (state = initialState, action) => {
  switch (action.type) {
    case 'ADD_LIST':
      return {
        lists: [...state.lists, action.payload],
      };
    case 'DONE_LIST':
      return {
        lists: state.lists.map((list) => {
          if (list.name !== action.payload) return list;
          return {
            ...list,
            complete: true,
          };
        }),
      };
    case 'DELETE_LIST':
      return {
        lists: state.lists.filter((list) => list.name !== action.payload),
      };

    default:
      return state;
  }
};

const store = createStore(reducer);

export default store;

作成したApp.jsxファイルは下記の通りです。


import { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';

function App() {
  const lists = useSelector((state) => state.lists);
  const dispatch = useDispatch();

  const [name, setName] = useState('');
  const [complete, setComplete] = useState(false);

  const inputText = (e) => {
    setName(e.target.value);
  };

  const addList = () => {
    if (!name) return;

    setComplete(false);

    dispatch({
      type: 'ADD_LIST',
      payload: {
        name,
        complete,
      },
    });
    setName('');
  };

  const doneList = (name) => {
    dispatch({ type: 'DONE_LIST', payload: name });
  };
  const deleteList = (name) => {
    dispatch({ type: 'DELETE_LIST', payload: name });
  };

  return (
    <div>
      <h1>ReduxでTodoリスト作成</h1>
      <input type="text" value={name} onChange={inputText} />
      <button onClick={addList}>追加</button>
      <h2>未完了のTodoリスト</h2>
      <ul>
        {lists
          .filter((list) => list.complete === false)
          .map((list, index) => (
            <div key={index}>
              {list.name}
              <button onClick={() => doneList(list.name)}>完了</button>
              <button onClick={() => deleteList(list.name)}>削除</button>
            </div>
          ))}
      </ul>
      <h2>完了したTodoリスト</h2>
      <ul>
        {lists
          .filter((list) => list.complete === true)
          .map((list, index) => (
            <div key={index}>{list.name}</div>
          ))}
      </ul>
    </div>
  );
}

export default App;

Redux Toolkitを利用した場合

Redux Toolkitを利用した場合の設定方法を確認します。

プロジェクトの作成

“npm create vite@latest”コマンドでReactのプロジェクトを作成します。プロジェクト名には”redux-toollkit-todo-list”, フレームワークには”React”、variantには”JavaScript”を選択しています。


% npm create vite@latest

> npx
> create-vite

✔ Project name: … redux-toollkit-todo-list
✔ Select a framework: › React
✔ Select a variant: › JavaScript

Scaffolding project in /Users/mac/Desktop/redux-toollkit-todo-list...

Done. Now run:

  cd redux-toollkit-todo-list
  npm install
  npm run dev

プロジェクトの作成が完了したらプロジェクトディレクトリredux-toollkit-todo-listに移動してnpm installコマンドを実行してJavaScriptライブラリのインストールを行い、その後redux-toolkitのインストールを行います。


% cd redux-toollkit-todo-list
% npm install
% npm install @reduxjs/toolkit react-redux

インストールしたライブラリとバージョンを確認するためpackage.jsonファイルを確認しておきます。


{
  "name": "redux-toollkit-todo-list",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
    "preview": "vite preview"
  },
  "dependencies": {
    "@reduxjs/toolkit": "^2.2.6",
    "react": "^18.3.1",
    "react-dom": "^18.3.1",
    "react-redux": "^9.1.2"
  },
  "devDependencies": {
    "@types/react": "^18.3.3",
    "@types/react-dom": "^18.3.0",
    "@vitejs/plugin-react": "^4.3.1",
    "eslint": "^8.57.0",
    "eslint-plugin-react": "^7.34.3",
    "eslint-plugin-react-hooks": "^4.6.2",
    "eslint-plugin-react-refresh": "^0.4.7",
    "vite": "^5.3.4"
  }
}

Storeの作成

srcディレクトリにstoreディレクトリを作成してその下にindex.jsファイルを作成して以下のコードを記述します。


import { configureStore } from '@reduxjs/toolkit';

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

Providerコンポーネントの設定

store/index.jsファイル作成後、作成したstoreにすべてのコンポーネントからアクセスできるようにmain.jsxファイルを更新します。react-reduxからimportしたProviderでAppコンポーネントを包みます。さらに作成したstoreをimportしてpropsとしてProviderコンポーネントに渡します。


import App from './App.jsx';
import { store } from './redux/index';
import { Provider } from 'react-redux';

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>
);

Sliceファイルの設定

storeディレクトリにtodoSlice.jsファイルを作成してRedux Toolkitで管理するTodoリストに関する設定を行います。Todoリストの初期値のみ設定を行なっています。


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

export const todoSlice = createSlice({
  name: 'todos',
  initialState: {
    lists: [
      {
        name: 'ブログを確認',
        complete: false,
      },
      {
        name: 'メールの返信',
        complete: false,
      },
    ],
  },
  reducers: {},
});

export default todoSlice.reducer;

設定を行ったらstore/index.jsのconfigureStoreでtodoSliceからimportしたtodoReducerを指定します。


import { configureStore } from '@reduxjs/toolkit';
import todoReducer from './todoSlice';

export const store = configureStore({
  reducer: {
    todos: todoReducer,
  },
});

App.jsxからのTodoリストのアクセス

App.jsxファイルからuseSelector Hookを利用してtodoSlice.jsファイルで設定したlistsにアクセスしてmap関数でブラウザ上に表示させます。


import { useSelector } from 'react-redux';

function App() {
  const lists = useSelector((state) => state.todos.lists);
  return (
    <div>
      <h1>ReduxでTodoリスト作成</h1>
      <h2>Todoリスト</h2>
      <ul>
        {lists.map((list, index) => (
          <li key={index}>{list.name}</li>
        ))}
      </ul>
    </div>
  );
}

export default App;

“npm run dev”コマンドで開発サーバを起動します。


% npm run dev

> redux-toollkit-todo-list@0.0.0 dev
> vite

Port 5173 is in use, trying another one...

  VITE v5.3.4  ready in 217 ms

  ➜  Local:   http://localhost:5174/
  ➜  Network: use --host to expose
  ➜  press h + enter to show help

ブラウザからhttp://localhost:5174にアクセスを行い、リストが表示されることを確認します。

ToDoリストを表示
ToDoリストを表示

リスト分け

取得したlistsに対してfilter関数を実行して完了リストと未完了リストの2つのリストにわけます。


import { useSelector } from 'react-redux';

function App() {
  const lists = useSelector((state) => state.todos.lists);
  return (
    <div className="App">
      <h1>ReduxでTodoリスト作成</h1>
      <h2>未完了のTodoリスト</h2>
      <ul>
        {lists
          .filter((list) => list.complete === false)
          .map((list, index) => (
            <div key={index}>{list.name}</div>
          ))}
      </ul>
      <h2>完了したTodoリスト</h2>
      <ul>
        {lists
          .filter((list) => list.complete === true)
          .map((list, index) => (
            <div key={index}>{list.name}</div>
          ))}
      </ul>
    </div>
  );
}

export default App;
Todoリストを2つに分ける
Todoリストを2つに分ける

リストの移動(未完了→完了)

Todoリストのcompleteプロパティの値をfalseからtrueに更新することで未完了から完了にリストを移動させます。

todoSlice.jsファイルのreducersにdoneList関数を追加します。doneList関数ではactionのpayloadから渡されるリストの名前を利用してどのリストを更新するか識別します。追加したdoneListはdispatch関数でコンポーネントから実行できるようにAction Creatorsとしてexportします。


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

export const todoSlice = createSlice({
  name: 'todos',
  initialState: {
    lists: [
      {
        name: 'ブログを確認',
        complete: false,
      },
      {
        name: 'メールの返信',
        complete: false,
      },
    ],
  },
  reducers: {
    doneList: (state, action) => {
      const { name } = action.payload;
      const item = state.lists.find((item) => item.name === name);
      if (item) {
        item.complete = true;
      }
    },
  },
});

export const { doneList } = todoSlice.actions;

export default todoSlice.reducer;

App.jsxファイルでは未完了のTodoリストが完了したら完了リストに移動できるように完了ボタンを追加します。onClickイベントを設定して、ボタンをクリックするとdoneList関数を実行し、どのリストか識別できるようにlist.nameを設定します。


import { useSelector, useDispatch } from 'react-redux';
import { doneList } from './store/todoSlice';

function App() {
  const lists = useSelector((state) => state.todos.lists);
  const dispatch = useDispatch();
  return (
    <div className="App">
      <h1>ReduxでTodoリスト作成</h1>
      <h2>未完了のTodoリスト</h2>
      <ul>
        {lists
          .filter((list) => list.complete === false)
          .map((list, index) => (
            <div key={index}>
              {list.name}
              <button onClick={() => dispatch(doneList({ name: list.name }))}>
                完了
              </button>
            </div>
          ))}
      </ul>
      <h2>完了したTodoリスト</h2>
      <ul>
        {lists
          .filter((list) => list.complete === true)
          .map((list, index) => (
            <div key={index}>{list.name}</div>
          ))}
      </ul>
    </div>
  );
}

export default App;

設定が完了したらブラウザ上で動作確認を行います。”ブログを確認”の右に表示されている”完了”ボタンをクリックします。

完了ボタンを押す前
完了ボタンを押す前

クリックすると完了したTodoリストに”ブログを確認”が移動します。

リストの移動
リストの移動

Redux Toolkitを利用した設定方法を確認することができました。リストの追加、削除もReduxで行った設定を参考にRedux Toolkitでも設定を行ってみてください。