React初心者がハマりがちな穴かも
タスク管理アプリを作成するに当たって、useReducerを使ってState内プロパティの値をいじる関数を作ろうとしたところ、いくつかつまずいた点があり、ググってもあまり情報が無かったので備忘録として残しておきます。('追加'と'削除'に関しては情報多かったけど'変更'に関しては…)
React初心者の方は参考にどうぞ。
ちなみにuseReducerの使い方を1から説明するような記事ではありません。
やりたかったこと
以下の画像に手書きで記している箇所をクリックすることで文字列を未完了⇄完了と自由に変更できるようにしたかった。なお今回の場合、完了or未完了はprogressというプロパティで管理しています。
試した方法
少しまわりくどい方法ですが、「'未完了'の場合は'完了'を、'完了'の場合は'未完了'を返す」という方法を取りました。すると最初は以下のようなコードになりました。
const tasks = (state = [], action) => {
switch(action.type) {
case CHANGE_INCOMP_TO_COMP: // 未完了→完了
const incompEventID = action.id - 1
return (
// console.log(state[incompEventID]) によってオブジェクトの取得は確認済み
state[incompEventID].progress = '完了'
)
case CHANGE_COMP_TO_INCOMP: // 完了→未完了
const compEventID = action.id - 1
return (
// console.log(state[compEventID])
state[compEventID].progress = '未完了'
)
default:
return state
}
}
console.log(state[compEventID])で目的とするオブジェクトが取得できていることを確認できました。
それならばstate[compEventID].progressに再代入すれば良いだろう、と。
早速yarn startで実行してみました。
ところが。
state.mapは関数じゃない!?
タスクリストの作成を担っているコンポーネント内で、以下のようなエラーが起こりました。
map関数の実行でこのようなエラーが起きた場合、stateが配列でなくなってしまったことが原因であると予測できます。
ReactDeveloperToolでstateを確認したところ、**「'未完了'('完了')」**と表示されました。配列どころかオブジェクトですらなくなり、ただの文字列となってしまっていたのです…。
エラーの原因はスプレッド構文!?
さて、React経験者の方であれば原因はとっくにお気づきのことかと思いますが、Reactにおいてstateの値を直接いじることは原則出来ません。初歩的ですが忘れがちなルールです。
今回実装したのは追加機能でも削除機能でもなく変更機能であったことと、Reducerという慣れない手法を用いたことでハマってしまった初歩的な穴でした。
stateを直接いじれないのであれば、「...state」というスプレッド構文の形でstateをコピーを取り出せば良いのです。
修正後のコードが以下となります。
const tasks = (state = [], action) => {
switch(action.type) {
case CHANGE_INCOMP_TO_COMP: // 未完了→完了
const Comp = {progress: '完了'}
Object.assign(
...state,
Comp
)
return [...state]
case CHANGE_COMP_TO_INCOMP: // 完了→未完了
const Incomp = {progress: '未完了'}
Object.assign(
...state,
Incomp
)
return [...state]
default:
return state
}
}
さて、ここでObject.assign()という関数が出てきました。
この関数はオブジェクト同士を結合させる機能を持つものですが、プロパティ名が被ったときは上書きしてくれるのです。
この場合、直前に宣言した「Comp(Incomp)」でstate内のプロパティ「progress」を上書きしたということですね。
これで完了⇄未完了の切り換え機能を実装することが出来ました。
最後に、先ほどエラーの起こったmap関数は以下のようにリファクタリングしました。
return (
<ul>
{Array.isArray(state.tasks) && state.tasks.map((task, index) => {
return (
<Task key={index} task={task} state={state} dispatch={dispatch}/>
)
})}
</ul>
)
Array.isArray()を使って**「stateが配列であるか」を判定し、trueであれば実行させるというような形でmap関数を保護**しました。
最後に
最後まで読んでいただきありがとうございました。
まだReactを覚えたばかりの駆け出しですので、間違った記載があれば、温かい目でご指摘頂けますと幸いです。
作成中ですが、本アプリケーションのURLは↓になります。
https://task-manegementapp.web.app/
以上です。