※当サイトの記事には、広告・プロモーションが含まれます。

フロントエンドの「状態管理」って何なのか。Reduxを使ってみる

www.itmedia.co.jp

⇧ 諸般の事情が気になるけども、利益が上がってなかったということなんでしょうね...購入する人が多くて利益が出ていれば、販売継続するものね。

アクセスの問題を無視した場合はグローバル変数と同じ?

Microsoft Bingに聞いてみたのですが、

グローバル変数と状態管理ライブラリのデータの保持の仕方には、大きな違いがあります。
グローバル変数は、どこからでもアクセスできるため、予期せぬ副作用を引き起こす可能性があります。
また、グローバル変数は、プログラムの規模が大きくなるにつれて管理が困難になります。
一方、状態管理ライブラリは、データをストアに保持するため、どこからでもアクセスできるわけではありません。
そのため、予期せぬ副作用を引き起こす可能性が低くなります。
また、状態管理ライブラリは、プログラムの規模が大きくなっても管理が容易です1。

ただし、アクセスの問題を無視した場合、グローバル変数と状態管理ライブラリのデータの保持の仕方に違いはありません。
どちらもJavaScriptの変数に保持しているためです2。

以上のように、グローバル変数と状態管理ライブラリのデータの保持の仕方には大きな違いがありますが、アクセスの問題を無視した場合は違いがありません。
  1. 良いコードの書き方 - Qiita
  2. 【JavaScript入門】グローバル(global)変数とは?宣言方法を解説! | ウェブカツBLOG
  3. 必要な情報を保存する — 変数 - ウェブ開発を学ぶ | MDN

⇧ 本当?

Reduxとグローバル変数の関係は?

Reactで「状態管理」というと、「Redux」というライブラリが有名らしいのだけど、

www.reddit.com

⇧ 上記サイト様での議論によりますと、確かに「グローバル変数」に対して様々な機能を付加しているってことで、『「Redux」=「グローバル変数」+ α』って考えちゃって良いんかな?

Reduxのコンセプトと「状態管理」

MVC」で実現できないことを実現するために「Flux」が作られて、「Flux」を改良したのが「Redux」ということだというようなことが、

www.clariontech.com

MVC is popular in both server-side and client-side frameworks. There is no shortage of front-end frameworks, which can support you to connect with MVC. AngularJS, Ember, Backbone, Sprout, and Knockout are a few examples. The MVC also shines on the backend frameworks or solutions like Spring, Ruby on Rails, Django, Meteor, etc. Flux and Redux, in contrast, are largely a front-end pattern. Flux addresses the problems of handling application state on the client-side. Hence, the front-end frameworks & libraries like Angular 2, Vue.js, and Polymer can all have a natural interaction with Flux.

https://www.clariontech.com/blog/mvc-vs-flux-vs-redux-the-real-differences

When comparing the usability of Flux vs Redux, both score the same. But Redux is not just a state management library, it offers several benefits for your front-end apps, including ensuring data consistency, sharing data between components and providing templates for code organization. Redux is primarily associated with React, but it can work well with other libraries as well, including Vue.js, AngularJS, Ember, Backbone.js, Meteor, and Polymer.

https://www.clariontech.com/blog/mvc-vs-flux-vs-redux-the-real-differences

⇧ 上記サイト様が仰っておりますと。

「Redux」の公式のドキュメントによりますと、

redux.js.org

⇧ というデータフローになるらしい。

「Redux」のコアコンセプトは3つあるらしく、

Single Source of Truth

The global state of your application is stored as an object inside a single store. Any given piece of data should only exist in one location, rather than being duplicated in many places.

https://redux.js.org/tutorials/fundamentals/part-2-concepts-data-flow#single-source-of-truth

This does not mean that every piece of state in your app must go into the Redux store! You should decide whether a piece of state belongs in Redux or your UI components, based on where it's needed.

https://redux.js.org/tutorials/fundamentals/part-2-concepts-data-flow#single-source-of-truth

⇧ アプリケーション全体で共有したいような『状態』を実現したいなら1つの「store」を使うってのが1つ目のコンセプトで、

State is Read-Only

The only way to change the state is to dispatch an action, an object that describes what happened.

This way, the UI won't accidentally overwrite data, and it's easier to trace why a state update happened. Since actions are plain JS objects, they can be logged, serialized, stored, and later replayed for debugging or testing purposes.

https://redux.js.org/tutorials/fundamentals/part-2-concepts-data-flow#state-is-read-only

⇧「store」で管理する『状態』を変更する唯一の方法は「dispatch」が生成する「action」というオブジェクトってのが2つ目のコンセプトで、

Changes are Made with Pure Reducer Functions

To specify how the state tree is updated based on actions, you write reducer functions. Reducers are pure functions that take the previous state and an action, and return the next state. Like any other functions, you can split reducers into smaller functions to help do the work, or write reusable reducers for common tasks.

https://redux.js.org/tutorials/fundamentals/part-2-concepts-data-flow#changes-are-made-with-pure-reducer-functions

⇧「action」というオブジェクトと『状態』を受け取った「reducer」という関数が、『状態』を更新するってのが3つ目のコンセプトらしい。

コンセプトとかを見る限り、やってることは、最終的には変数の参照と更新なので、「状態管理」というのは乱暴に言ってしまえば「特定の方法に準拠した変数の参照と更新」ってことになるんですかね?

「変数」に他ならないからこそ、

github.com

Persist and rehydrate a redux store.

https://github.com/rt2zz/redux-persist

⇧ redux storeの永続化という話が出てくるんでしょうかね。

qiita.com

主に、次の機能によって、Storeの状態を永続化します。

  • ReduxのStoreの状態(State)を localStorage (Web)や、 AsyncStorage (react-native)などのStorageに保存する
  • Storageに保存した状態をアプリ起動時に読み込ませる

Storeの永続化にredux-persistを使う - Qiita

⇧ 上記サイト様がまとめて下さっております。

ブラウザのリロードとかした場合でもデータを保持したいケースでは、「Redux」だけでは対応できないっぽいですと。

ちなみに、Vueだと「Vuex」という「状態管理」に関するライブラリがあるようなのですが

www.merixstudio.com

torounit.com

qiita.com

var.blog.jp

⇧ 上記サイト様によりますと、「Redux」「Vuex」ともに「Flux」の考え方にインスパイアされてはいるものの、それぞれで独自な実装が行われてるようです。

Reduxの使いどころとかって?

「Redux」のユースケースがいまいち思い浮かばないのだけど、

zenn.dev

⇧ 上記サイト様によりますと、「Redux」を使う時の注意事項みたいなものも変わりつつあるらしい。

TypeScriptでReduxを利用するのに必要なものは?

とりあえず、

redux.js.org

qiita.com

reduxはredux自身に型定義がされているので、@types/reduxのインストールは不要です。

React + Redux + TypescriptでサンプルWebアプリ - Qiita

⇧ Reduxの公式のドキュメントとネットの情報を見た感じでは、React + TypeScriptでReduxを利用するには、

  1. Redux
    • redux
  2. React Redux
    • react-redux
    • @types/react-redux
  3. Redux Toolkit (RTK)
    • @reduxjs/toolkit

のnpmライブラリをインストールする感じになるらしい。

「Redux Toolkit(RTK)」が何なのかと言うと、

github.com

The official, opinionated, batteries-included toolset for efficient Redux development

(Formerly known as "Redux Starter Kit")

https://github.com/reduxjs/redux-toolkit

⇧ Reduxを利用した開発を効率化してくれるものらしいが、全く謎過ぎますな...

redux-toolkit.js.org

As described in the Quick Start page, the goal of Redux Toolkit is to help simplify common Redux use cases. It is not intended to be a complete solution for everything you might want to do with Redux, but it should make a lot of the Redux-related code you need to write a lot simpler (or in some cases, eliminate some of the hand-written code entirely).

https://redux-toolkit.js.org/usage/usage-guide

⇧ う~む、そもそもReduxを使ったことがないから、いまいち、「Redux Toolkit(RTK)」を利用することのメリット・デメリットが実感できそうにないので、今回は、「Redux Toolkit(RTK)」を利用しない方向で。

ちなみに、

qiita.com

Reduxは2021年11月現在Reactで最も使われている状態管理ライブラリです。つまり、現在多くのプロジェクトで導入されており、Reduxを用いたプロジェクトに関わる可能性は非常に高いです。もう用語を見ただけでうんざりな人もいると思いますが、最低限の知識はつけておく必要があるでしょう。しかし、個人的には新規のプロダクトでReduxを採用するのはおすすめしません(便利かどうかは置いといてめんどくさすぎませんか?)。要するに言いたいことは新規開発で使うのはおすすめしないけど、プロジェクトとかではみんな使ってるから使えないとまずいよねって話です。

今から始めるRedux x React x TypeScript - Qiita

⇧ ということらしいので、「Redux」に触れざるを得ない機会は少なくないようです...

2023年5月2日(火)追記:↓ ここから

どうやら、

zenn.dev

createStore@deprecatedになり非推奨としてマークされるようになった。そしてlegacy_createStoreが追加された。これは、Redux Toolkitをへの移行を促すためである。

Weekly Frontend News 2022年4月4週目/React Redux v8,Redux v4.2,Nuxt v3 RCなど

また、Why Redux Toolkit is How To Use Redux Today が追加され、ReduxではなくRedux Toolkitを使うべき理由が説明されている。

Weekly Frontend News 2022年4月4週目/React Redux v8,Redux v4.2,Nuxt v3 RCなど

⇧「Redux」を使うなら「Redux Toolkit」の方を使うことが推奨されるらしい...

学習コストが嵩むなぁ...

2023年5月2日(火)追記:↑ ここまで

Reduxを使ってみる

とりあえず、利用するプロジェクトは、

ts0818.hatenablog.com

⇧ 前回の記事のもので。

まずは、必要なnpmライブラリをインストールします。

Reduxについては、特に必要な条件はないようなので、alpha版以外も存在する中では最新っぽい4.2.1をインストールしました。

で、ReactでReduxを使うために必要なreact-reduxについては、reactのバージョンに依存しているらしく、2023年5月1日(月)時点だと、

github.com

React Redux 8.0 requires React 16.8.3 or later (or React Native 0.59 or later).

https://github.com/reduxjs/react-redux/tree/master

⇧ react-reduxのバージョンは8.0.xが最新らしく、reactのバージョンは16.8.3以上である必要がありますと。

ということで、現時点の最新をインストールしました。

「@types/react-redux」については、必要な条件とかが見当たらなかったので、現時点での最新版をインストールしました。

あとは、Reduxを利用した開発用にあると便利らしい「redux-devtools」をインストールしたかったのですが、エラーが出てしまい、

npm ERR! code ERESOLVE
npm ERR! ERESOLVE unable to resolve dependency tree
npm ERR!
npm ERR! While resolving: my-react-spa-app@0.0.0
npm ERR! Found: react@18.2.0
npm ERR! node_modules/react
npm ERR!   react@"^18.2.0" from the root project
npm ERR!
npm ERR! Could not resolve dependency:
npm ERR! peer react@"^0.14.9 || ^15.3.0 || ^16.0.0" from redux-devtools@3.7.0
npm ERR! node_modules/redux-devtools
npm ERR!   dev redux-devtools@"3.7.0" from the root project       
npm ERR!
npm ERR! Fix the upstream dependency conflict, or retry
npm ERR! this command with --force or --legacy-peer-deps
npm ERR! to accept an incorrect (and potentially broken) dependency resolution.
npm ERR! 
npm ERR!
npm ERR! For a full report see:
npm ERR! C:\Users\Toshinobu\AppData\Local\npm-cache\_logs\2023-05-01T05_43_45_461Z-eresolve-report.txt

npm ERR! A complete log of this run can be found in:
npm ERR!     C:\Users\Toshinobu\AppData\Local\npm-cache\_logs\2023-05-01T05_43_45_461Z-debug-0.log

⇧ 何やら、reactのバージョンが合って無さそうな感じで怒られてそう。

ネットの情報を確認してみたところ、

zenn.dev

⇧ peerDependenciesってのが、解決できてないってことらしく、

⇧「redux-devtools」が「react」「react-redux」「redux」の最新バージョンに対応できていないっぽい...

とりあえず、今回は、「redux-devtools」をインストールするのは諦めました。

ライブラリの追加ができたか、package.jsonを確認。

■C:\Users\Toshinobu\Desktop\soft_work\react_work\my-react-spa-app\package.json

{
  "name": "my-react-spa-app",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "@types/react-redux": "^7.1.25",
    "@types/react-router-dom": "^5.3.3",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-redux": "^8.0.5",
    "react-router-dom": "^6.10.0",
    "redux": "^4.2.1"
  },
  "devDependencies": {
    "@types/react": "^18.0.28",
    "@types/react-dom": "^18.0.11",
    "@vitejs/plugin-react": "^3.1.0",
    "typescript": "^4.9.3",
    "vite": "^4.2.0"
  }
}
    

⇧ 追加されてます。@types系のライブラリは、devDependeciesに追加したほうが良かったのかな?

ライブラリは準備できたので、「状態管理」の様子を確認する用に<form>を追加することにします。(<form>じゃなくても良いと思いますが、入力となると<form>がパッと思い付いただけです)

qiita.com

zenn.dev

⇧ 上記サイト様を参考に、「components」「stores」ディレクトリを追加してみます。

最近、携わってるシステムのフォームの送信ボタンが<form>の外側にあるので、

tech-1natsu.hatenablog.com

stackoverflow.com

⇧ 上記サイト様を参考に、外側にボタンを配置する感じで。

プロジェクトの構成は以下のような感じです。

C:.
│  .gitignore
│  index.html
│  package-lock.json
│  package.json
│  tsconfig.json
│  tsconfig.node.json
│  vite.config.ts
│  
├─node_modules
│  │  .package-lock.json
│  │  
│     ...省略
│      
│  ├─.bin
│  │
│     ...省略
│
│
├─public
│      vite.svg
│      
└─src
    │  App.css
    │  App.tsx
    │  index.css
    │  main.tsx
    │  tree.txt
    │  vite-env.d.ts
    │  
    ├─assets
    │      react.svg
    │      
    ├─components
    │  ├─functional
    │  │  └─form
    │  │          index.ts
    │  │          
    │  ├─model
    │  │  ├─dto
    │  │  └─form
    │  │      ├─book
    │  │      │      index.ts
    │  │      │      
    │  │      └─food-beverage-liqour
    │  ├─page
    │  │  ├─book
    │  │  │  │  index.tsx
    │  │  │  │  
    │  │  │  ├─children-book
    │  │  │  ├─commic
    │  │  │  │      index.tsx
    │  │  │  │      
    │  │  │  ├─foreign-book
    │  │  │  ├─hard-cover
    │  │  │  ├─magazine
    │  │  │  ├─paper-book
    │  │  │  ├─picture-book
    │  │  │  └─pocket-edition-book
    │  │  └─food-beverage-liqour
    │  │      ├─food-beverage
    │  │      └─liqour
    │  │          ├─beer
    │  │          ├─chuhai-and-cocktail
    │  │          ├─low-malt-beer
    │  │          ├─non-alcoholic
    │  │          ├─sake
    │  │          ├─shouchu
    │  │          ├─western-liqours-and-liquers
    │  │          └─wine
    │  └─ui
    │      └─button
    │          │  Button.tsx
    │          │  
    │          └─form
    │                  Button.tsx
    │                  
    ├─page
    │  │  404.tsx
    │  │  
    │  ├─about
    │  │      index.tsx
    │  │      
    │  ├─book
    │  │  │  index.tsx
    │  │  │  
    │  │  ├─children-book
    │  │  │      index.tsx
    │  │  │      
    │  │  ├─commic
    │  │  │      index.tsx
    │  │  │      
    │  │  ├─foreign-book
    │  │  │      index.tsx
    │  │  │      
    │  │  ├─hard-cover
    │  │  │      index.tsx
    │  │  │      
    │  │  ├─magazine
    │  │  │      index.tsx
    │  │  │      
    │  │  ├─paper-back
    │  │  │      index.tsx
    │  │  │      
    │  │  ├─picture-book
    │  │  │      index.tsx
    │  │  │      
    │  │  └─pocket-edition-book
    │  │          index.tsx
    │  │          
    │  ├─food-beverage-liqour
    │  │  │  index.tsx
    │  │  │  
    │  │  ├─food-beverage
    │  │  │      index.tsx
    │  │  │      
    │  │  └─liqour
    │  │      │  index.tsx
    │  │      │  
    │  │      ├─beer
    │  │      │      index.tsx
    │  │      │      
    │  │      ├─chuhai-and-cocktail
    │  │      │      index.tsx
    │  │      │      
    │  │      ├─low-malt-beer
    │  │      │      index.tsx
    │  │      │      
    │  │      ├─non-alcoholic
    │  │      │      index.tsx
    │  │      │      
    │  │      ├─sake
    │  │      │      index.tsx
    │  │      │      
    │  │      ├─shouchu
    │  │      │      index.tsx
    │  │      │      
    │  │      ├─western-liqours-and-liquers
    │  │      │      index.tsx
    │  │      │      
    │  │      └─wine
    │  │              index.tsx
    │  │              
    │  ├─home
    │  │      index.tsx
    │  │      
    │  ├─layout
    │  │      content.tsx
    │  │      footer.tsx
    │  │      header.tsx
    │  │      helper-sidebar.tsx
    │  │      index.tsx
    │  │      sidebar.tsx
    │  │      
    │  └─login
    │          index.tsx
    │          
    ├─routes
    │      route-book.ts
    │      route-food-beverage-liquor.ts
    │      route.ts
    │      router.tsx
    │      
    └─stores
        │  store.ts
        │  
        ├─actions
        │      bookAction.ts
        │      commonAction.ts
        │      
        ├─constants
        │  └─actions
        │          index.ts
        │          
        └─reducers
                bookReducer.ts

⇧ フロントエンドの知見が無いので、プロジェクトの構成は適当です。

今回、追加したソースコードは以下。

Reduxに関わる部分

■C:\Users\Toshinobu\Desktop\soft_work\react_work\my-react-spa-app\src\stores\constants\actions\index.ts

/** identifier for action in redux */ 
// Reduxの状態管理をリセットするActionの識別子
export const ACTIONS_CLEAR_ALL_STATE = 'ACTION_CLEAR_ALL_STATE'
// Formの状態管理のActionの識別子
export const ACTIONS_UPDATE_TEXT_INPUT_VALUE = 'ACTIONS_UPDATE_TEXT_INPUT_VALUE';
export const ACTION_UPDATE_SELECTED_VALUE = 'ACTION_UPDATE_SELECTED_VALUE';    

■C:\Users\Toshinobu\Desktop\soft_work\react_work\my-react-spa-app\src\stores\actions\commonAction.ts

import * as types from '../constants/actions'

export const actionClearAllState = () => {
    type: types.ACTIONS_CLEAR_ALL_STATE
}    

■C:\Users\Toshinobu\Desktop\soft_work\react_work\my-react-spa-app\src\stores\actions\bookAction.ts

import * as types from '../constants/actions'

export const actionsUpdateTextInputValue = (text: string) => ({
    type: types.ACTIONS_UPDATE_TEXT_INPUT_VALUE
    ,payload: text
})
export const actionUpdateSelectedValue = (text: string) => ({
    type: types.ACTION_UPDATE_SELECTED_VALUE
    ,payload: text
})    

■C:\Users\Toshinobu\Desktop\soft_work\react_work\my-react-spa-app\src\stores\reducers\bookReducer.ts

import {
  ACTIONS_UPDATE_TEXT_INPUT_VALUE
  ,ACTION_UPDATE_SELECTED_VALUE
} from '../constants/actions'

interface State {
    inputValue: string
    selectedValue: string
}

const initialState: State = {
  inputValue: ''
  ,selectedValue: ''
}

const bookReducer = (state: any=initialState, action: any) => {
  devLog(state,action)
  switch (action.type) {
    case ACTIONS_UPDATE_TEXT_INPUT_VALUE:
        let inputState = {...state};
        //inputState.inputValue = action.payload;
        return {
            inputState
            ,...action.payload
        }
    case ACTION_UPDATE_SELECTED_VALUE:
        let selectState = {...state};
        selectState.selectedValue = action.payload;
        return {
            selectState
            ,...action.payload
        }
    default:
        return state;
  }
}

const devLog = (...args: Array<object>) => {
    console.log("■start■bookReducers#devLog")
    args.map((object, index) => {
        console.log(index)
        console.dir(object)
    })
    console.log("■end■bookReducers#devLog")
}

export default bookReducer;
    

■C:\Users\Toshinobu\Desktop\soft_work\react_work\my-react-spa-app\src\stores\store.ts

// redux
import { legacy_createStore, applyMiddleware, combineReducers} from 'redux';
import bookReducer from './reducers/bookReducer';

const appReducer = combineReducers({
    bookReducer: bookReducer
});

const rootReducer = (state: any, action: any) => {
  if (action.type === "ACTION_CLEAR_ALL_STATE") {
    state = undefined;
  }
  return appReducer(state, action);
}

const store = legacy_createStore(
    rootReducer
    , applyMiddleware()
)

export default store    

formとか

■C:\Users\Toshinobu\Desktop\soft_work\react_work\my-react-spa-app\src\components\model\form\book\index.ts

// src/components/model/form/book
const bookForm = [
    {
        property: "タイトル"
        ,id: "book-title"
        ,name: "book-title"
        ,element: "input"
        ,type: "text"
    }
    ,{
        property: "著者"
        ,id: "book-author"
        ,name: "book-author"
        ,element: "input"
        ,type: "text"      
    }
    ,{
        property: "国"
        ,id: "publishing-country"
        ,name: "publishing-country"
        ,element: "selectbox"
        ,type: ""      
    }
    ,{
        property: "出版社"
        ,id: "publishing-company"
        ,name: "publishing-company"
        ,element: "input"
        ,type: "text"        
    }
]

export default bookForm;    

■C:\Users\Toshinobu\Desktop\soft_work\react_work\my-react-spa-app\src\components\ui\button\Button.tsx

import React from 'react';

interface ButtonInterface {
    title: string
    onClick: (e: React.MouseEvent<HTMLButtonElement>) => void
}

const Button: React.FC<ButtonInterface> = ({title, onClick}: ButtonInterface) => {
    return (
      <button onClick={onClick}>{title}</button>
    )
}

export default Button;    

■C:\Users\Toshinobu\Desktop\soft_work\react_work\my-react-spa-app\src\components\ui\button\form\Button.tsx

import React from 'react';

interface ButtonInterface {
    title: string
    onClick: (e: React.MouseEvent<HTMLButtonElement>) => void
    formId: string
}

const FormButton: React.FC<ButtonInterface> = ({title, onClick, formId}: ButtonInterface) => {
    return (
      <button form={formId} onClick={onClick}>{title}</button>
    )
}

export default FormButton;    

■C:\Users\Toshinobu\Desktop\soft_work\react_work\my-react-spa-app\src\components\functional\form\index.ts

import {FormEvent} from 'react';
// フックを呼び出してよいのは React が関数コンポーネントをレンダーしている最中のみ
//import {useSelector} from 'react-redux';
// redux
import store from '../../../stores/store';
import {
    ACTIONS_CLEAR_ALL_STATE
  } from '../../../stores/constants/actions'

class formFunctional {

    static handleOnSubmit = async (event: FormEvent<HTMLFormElement>) => {
        event.preventDefault();
        const form = event.target as HTMLFormElement;
        const formData = new FormData(form);
        const requestData = Object.fromEntries(formData.entries());
        console.log("onSubmit()");
        console.log(requestData)
        window.alert("送信");

        // TODO:サーバーサイドとのやり取り
        try {
            // サーバーサイドへフォームの内容を送信
           
        } catch (error: unknown) {

        }
        // Reduxの状態管理をクリアー
        //const dispatch = useDispatch(); // hookを呼べないのでコメントアウト
        console.log("store.dispatch")
        store.dispatch({
            type: ACTIONS_CLEAR_ALL_STATE
        });
        // フォームの内容をクリアー
        form.reset()
        console.log(store.getState)
        console.log(form)
    };
}

export default formFunctional

■C:\Users\Toshinobu\Desktop\soft_work\react_work\my-react-spa-app\src\components\page\book\index.tsx

import react, {ReactNode} from "react";
import bookForm from "../../model/form/book";
import functionForm from "../../functional/form";
// redux
import {useDispatch, useSelector} from 'react-redux';
import { useState, useCallback } from 'react';
//import bookReducer from '../../../stores/reducers/bookReducer';
import {
  ACTIONS_UPDATE_TEXT_INPUT_VALUE
  ,ACTION_UPDATE_SELECTED_VALUE
} from '../../../stores/constants/actions'

const BookContent: React.FC = () => {
    const formId = "book";
    // TODO:サーバーサイドから取得する形にする
    const selectListCountry = [
       {key: "all", value: "All", label: ""} 
        , {key: "japan", value: "Japan", label: "日本"} 
        , {key: "china", value: "China", label: "中国"} 
        , {key: "america", value: "America", label: "アメリカ"} 
        , {key: "russian", value: "Russian", label: "ロシア"} 
        , {key: "uk", value: "UK", label: "イギリス"} 
        , {key: "france", value: "France", label: "フランス"} 
    ];

    //const [input, updateInput] = useState("");

    const dispatch = useDispatch();

    const bookState = useSelector(  
        (state:any) => {
          console.log('State: ', state);
          return state.bookReducer
        }
    );
    console.log("■bookState")
    console.log(bookState)

    const onChangeInput = useCallback(
        (event: React.ChangeEvent<HTMLInputElement>) => {
        const value = event.target.value;
        dispatch({
        type: ACTIONS_UPDATE_TEXT_INPUT_VALUE
        ,payload: value
        })
    }
    ,[dispatch]
    );

    const onChangeSelect = useCallback(
        (event: React.ChangeEvent<HTMLSelectElement>) => {
          const value = event.target.name
          dispatch({
            type: ACTION_UPDATE_SELECTED_VALUE
            ,payload: value
          })
        }
        ,[dispatch]
    );

    return(
      <div>
        <button form={formId}>検索</button>
        <form id={formId} onSubmit={functionForm.handleOnSubmit}>       
          <>
          {bookForm.map((val, index) => {
            
            if(val.element === 'input') {
              return(
                <div key={val.id}>
                  <label id={val.id}>{val.property}:
                    <input type={val.type} id={val.id} name={val.name}
                      onChange={onChangeInput}
                    />
                  </label>
                </div>
              )
            }
            if(val.element === 'selectbox') {

              return(
                <div key={val.id}>
                  <label id={val.id}>{val.property}:
                    <select id={val.id} name={val.name}
                      onChange={onChangeSelect}
                      defaultValue={selectListCountry
                        ? selectListCountry[0].value
                        :"All"
                      }
                    >         
                      <>
                        {selectListCountry.length > 0 
                         && selectListCountry.map((list, index) => {
                           return(
                             <option key={list.key} value={list.value}>{list.label}</option>
                           )      
                        })}
                      </>
                    </select>
                  </label>
                </div>
              )
            }
          })}
          </>
        </form>
      </div>
    );
   
}

export default BookContent;

■C:\Users\Toshinobu\Desktop\soft_work\react_work\my-react-spa-app\src\components\page\book\commic\index.tsx

import React from 'react'
import Button from "../../../ui/button/Button"

const handleClick = () => {
    alert("Buttonがクリックされました")
}

const CommicContent: React.FC = () => {
    console.log("components/page/book/commic")
    return (
        <>
          <Button title={'漫画'} onClick={handleClick} />
          
        </>
    );
};

export default CommicContent;  

■C:\Users\Toshinobu\Desktop\soft_work\react_work\my-react-spa-app\src\page\book\commic\index.tsx

// src/page/book/commic/index.tsx
import React from 'react'
import CommicContent from '../../../components/page/book/commic';

const Commic: React.FC = () => {
    return (
        <div>
            <h1>Commic</h1>
            <CommicContent />
        </div>
    );
}

export default Commic  

■C:\Users\Toshinobu\Desktop\soft_work\react_work\my-react-spa-app\src\page\book\index.tsx

// src/page/book/index.tsx
import React from 'react'
import { Outlet, useLocation } from 'react-router-dom';
import BookContent from '../../components/page/book'

const Book: React.FC = () => {
    const location = useLocation();
    return (
        <div>
            {location.pathname === "/book" && <h1>Book</h1>}
            <Outlet />
            <BookContent/>
        </div>
    );
}

export default Book

■C:\Users\Toshinobu\Desktop\soft_work\react_work\my-react-spa-app\src\main.tsx

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

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

で、

入力した値がReduxで管理されるようです。

submitボタンを押下して表示される「OK」ボタン押下。

Reduxで「状態管理」されていた値と、<form>の値がクリアーされました。

とりあえず、Reduxの動作は確認できたけども、使い方がよく分からんですな...

Reduxに関わるデータ型とかも分からんからany型になってしまうし...

<form>の分割の仕方もいまいち分からん...

なんか、「Redux」を使っている場合、

qiita.com

form値をreduxの管理下に置くのは原則よろしくないということで、今後はRedux Formでないものを使わねばならないようです。

React Hook Form 入門 - Qiita

⇧ formもライブラリを使う必要があるんかな?

あんまりライブラリに依存したくないんだけどな...

毎度モヤモヤ感が半端ない...

今回はこのへんで。