jotai は useState 的にグローバルに状態を管理する React のライブラリです。Redux 等の他のライブラリよりも小さくまとまっていて、React 組み込みのコンテキストよりも少ないコードで適切なレンダリング制御をしてくれます。
pmndrs/jotai: 👻 Primitive and flexible state management for React
Jotai, primitive and flexible state management for React
この記事で紹介する方法はルートコンポーネント、ルートのあたりでコンテキスト的に呼び出しているフックに対して適用するとすっきりするソースコードの整理方法です。props のリレーが起きている場合は特に改善されます。無暗にグローバルにすると管理が大変手間になり、どこを弄ったらどこが変わるのか予測困難になりやすくなります。
やることは useState を使用している部分を atom と useAtom に差し替えるだけです。
/** * 変更前 * この export したフックの返り値をリレーしたりコンテキストにまとめたりして使用 */ type DisplayModeType = 'GANTT_CHART' | 'RADAR_CHART' | 'BAR_GRAPH'; export const useMode = () => { const [mode, setMode] = useState<DisplayModeType>('GANTT_CHART'); const setModeWrapper = (newMode: DisplayModeType) => { // モード変更に必要ななんやかんや } return { mode, setMode: setModeWrapper, }; }; /** * 変更後 * state が atom によってコンポーネントを跨いでも同一のものになっています。 * このため下記の使用例の様に二か所でいきなり useMode を呼んでも同じ mode が返ってきます。 */ import {atom, useAtom} from "jotai"; type DisplayModeType = 'GANTT_CHART' | 'RADAR_CHART' | 'BAR_GRAPH'; const modeAtom = atom('GANTT_CHART') export const useMode = () => { const [mode, setMode] = useAtom(modeAtom); const setModeWrapper = (newMode: DisplayModeType) => { // モード変更に必要ななんやかんや } return { mode, setMode: setModeWrapper, }; }; /** * 使用例 * 単にフックを呼ぶだけで同じ mode を扱えます */ const HogePage = () => { // この setMode で mode を変えると FugaPage の方でも変わります const {mode, setMode} = useMode(); return <div>なんやかんや</div> } const FugaPage = () => { // この setMode で mode を変えると HogePage の方でも変わります const {mode, setMode} = useMode(); return <div>なんやかんや</div> } const PiyoPage = () => { // useAtom を使うことでフックを無視して読み取りだけを行うこともできます const [mode] = useAtom(modeAtom);// useAtom は useState 的に配列 return <div>なんやかんや</div> }
具体的なロジックを書き換えずとも作り変えられるのでずいぶん楽です。外部から初期値を渡す場合は次の様に useEffect を使うことで対応できます。例では雑に最初に呼ばれた時に一度だけ初期化ですが、適切に useEffect を使うことでより細やかな制御も可能です。
const modeAtom = atom('GANTT_CHART'); const initializedAtom = atom(false); // 一度だけ初期化したい場合に用意 export const useMode = (defaultMode: DisplayModeType) => { const [mode, setMode] = useAtom(modeAtom); const [initialized, setInitialized] = useAtom(initializedAtom); // useEffectを使うことにより最初のマウント時に引数で渡された値やReact内の計算が必要な処理で初期化 useEffect(() => { // 一度だけ初期化する様にフラグ判定 if (initialized) { return; } setInitialized(true); setMode(defaultMode); }, []);