Open1

jotaiの自作ユーティリティ置き場

じょうげんじょうげん
import { atom, type WritableAtom } from "jotai";
import { selectAtom } from "jotai/utils";

const judge = <Value>(cur: Value, prev: Value | undefined):Value => (!prev ? cur : prev);

export const atomWithStorageOnce = <Value, Args extends unknown[], Result>(
  anAtom: WritableAtom<Value, Args, Result>,
) =>
  atom(
    (get) => get(selectAtom<Value, Value>(anAtom, judge)),
    (_, set, ...update: Args) => set(anAtom, ...update),
  );

atomWithStorageを使っているatomで、初回マウント時に初期値としてstorageに保存してあるatomの状態にアクセスしたいが、それ以降はそのコンポーネントが持つuseStateを使用するようにし、書き込み方向のアクセスのみを行いたい場合に使えるユーティリティ。
このユーティリティ経由でatomを使用するようにすれば、コンポーネント自身はクソデカオブジェクトのatomの変更に反応することなく、自身の状態の変更のみに反応することができる。
更にstartTransitionと併用することで、画面の更新を並列で処理できる。
judge関数を外出ししているのは、外出ししないと関数が実行毎に作成されて無限ループが発生してしまうため。

使い方

import { atomWithStorageOnce } from "@/utils";
import { atomWithStorage } from "jotai/utils";
import { type Dispatch, type SetStateAction, startTransition, useCallback, useState } from "react";

const localStorageAtom = atomWithStorage("key",atom(0));

export const useNumberState = () => {
  const anAtom = atomWithStorageOnce(localStorageAtom);
  const [initialValue, setValue] = useAtom(anAtom);
  const [state, setState] = useState(initialValue);

  const dispatch: Dispatch<SetStateAction<number>> = useCallback(
    (value) => {
      setState(value);
      startTransition(() => setValue(value));
    },
    [setValue],
  );

  return [state, dispatch] as const;
};