はじめに
弊社のプロダクトでは、アラートの状態をRedux Toolkitで管理しています。
詳細な実装については今回は触れませんが、例えば「保存しました」というアラートを表示したいときは以下のように書いています。
dispatch(alertActions.open({ description: '保存しました' }))
このdescriptionにstring型ではなく、ReactNode型を渡す必要がありました。
そこで型を変更してみると、見たことのないエラーが発生しました。
その理由と、解消方法について紹介します。
先に結論を書きます。
- Redux storeにシリアライズ化できない値を格納するのは非推奨
-
serializableCheck
を無効にすれば、シリアライズ化できない値を格納することが可能になる -
serializableCheck
は以下のように個別で設定できる
configureStore({
//...
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
// Ignore these action types
ignoredActions: ['your/action/type'],
// Ignore these field paths in all actions
ignoredActionPaths: ['meta.arg', 'payload.timestamp'],
// Ignore these paths in the state
ignoredPaths: ['items.dates'],
},
}),
})
余談
なぜReactNode型を渡す必要があったのかについて、触れておきます。
プロダクトを多言語対応させるために、react-i18nextを導入しています。
基本的にはt
関数を使用してt('保存しました')
のようにラベルを指定しています。しかし、<a>
タグや<br>
タグなどを含めたラベルにしたい場合、t
関数だけでは実装できません。このようなときに<Trans>
コンポーネントを使用します。
この<Trans>
コンポーネントの返り値はReactNode型です。今回は<Trans>
コンポーネントを使用したかったため、型を変更する必要がありました。
発生したエラー
descriptionにReactNode型を渡せるようにするため、description: string | ReactNode
に変更しました。
そしてアラートを表示したとき事件が起こりました🚨
A non-serializable value was detected in the state, in the path: `alerts.description.$$typeof`. Value:
["symbol" failed to stringify]
Take a look at the reducer(s) handling this action type:alerts/open.
(See https://redux.js.org/faq/organizing-state#can-i-put-functions-promises-or-other-non-serializable-items-in-my-store-state)
「A non-serializable value was detected in the state」ってなんだ?🧐
調べてみましょう!
シリアライズ化ってなんですの??
シリアライズ化とは、データ構造やオブジェクトを保存や送信が可能な形式(通常は文字列形式)に変換するプロセスのことです。
シリアライズ化ができない値には、関数、Promise、Symbol、MapやSetなどの特殊なオブジェクト、クラスインスタンスなどが含まれます。これらの値は、シリアライズ化の過程で失われたり、正しく再現できなかったりするため、シリアライズ化には適していません。
エラー文から、今回はSymbolをシリアライズ化できないよと言われているようです😢
storeにシリアライズ化できない値を格納するのは非推奨
エラー文に載っていた公式ドキュメントを調べてみました。
It is highly recommended that you only put plain serializable objects, arrays, and primitives into your store. It's technically possible to insert non-serializable items into the store, but doing so can break the ability to persist and rehydrate the contents of a store, as well as interfere with time-travel debugging.
「storeには、プレーンなシリアライズ可能なオブジェクト、配列、プリミティブ値のみを格納することを強く推奨する」と書かれていますね。
シリアライズ化できない値を入れることもできるけど、非推奨のようです。
解決方法
シリアライズ化できない値を入れることは推奨はされませんが、storeの設定を変更すれば可能です🎉
公式ドキュメントで方法が紹介されていました。こちらを参考に、storeの設定を変更してみました。
export const store = configureStore({
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: ['alerts/open'],
ignoredPaths: ['alerts.description']
}
}),
// ...
これでアラートを表示してもエラーは発生しなくなりました。
疑問
なぜシリアライズ化できない値を入れることが非推奨??
そもそもなぜシリアライズ化できない値を入れることが非推奨なのでしょうか。
実は公式ドキュメントにも書かれているこの理由がよくわかリません😢
1つはRedux DevToolsなどのデバッグツールがうまく機能しないからです。これは実際にRedux DevToolsで検証できたので納得できました。storeの設定を変更後にデバッグしてみたところ、descriptionで表示しているラベルを確認することはできませんでした。デバッグしてもstoreに格納されている情報を見ることができなくなってしまうから、非推奨ということだと思われます。
他の理由がわかりませんでした。
but doing so can break the ability to persist and rehydrate the contents of a store,
直訳すると「storeの内容の永続化と再水和化の機能が損なわれる」ですが、永続化と再水和化が何を指すのかがわかりません…ここは別途調べておこうと思います💡
$$typeof
ってなんですの??
ReactNode型でdescriptionを指定しましたが、エラー文で引っかかったのは「Symbolをシリアライズ化できない」でした。
よくエラー文を読んでみると、alerts.description.$$typeof
と書いてあります。想像ですが、React Nodeには$$typeof
プロパティが存在するのではないでしょうか。そしてこの$$typeof
プロパティがSymbol型なのではないでしょうか。ここも別途調査したいです。
終わりに
公式で非推奨になっている内容を設定を変更することで無理やり実装しました。
アラートに関する情報はstoreに保持し続けておく必要がないし、デバッグで表示内容を確認したいこともなさそうなので、今のところは問題なさそうです。
「永続化と再水和化」、「$$typeof
」については、調べておこうと思います。