グローバルナビゲーションへ

本文へ

フッターへ

お役立ち情報Blog



jotai v2.2.0 で入った atomWithDefault の破壊的変更による影響と対策

jotai v2.2.0 で atomWithDefault に破壊的変更が入った

// suppose we have this
const asyncAtom = atom(() => Promise.resolve(1));
const countAtom = atomWithDefault((get) => get(asyncAtom));
// and in component
const setCount = useSetAtom(countAtom);
// previously,
setCount((c) => c + 1); // it worked, but it will no longer work

// instead, you need to do this
setCount((countPromise) => countPromise.then((c) => c + 1));

引用:https://github.com/pmndrs/jotai/releases/tag/v2.2.0

これまで非同期 atom(async atom)から atomWithDefault で派生した atom を更新するには同期なインターフェースとなっていたが、非同期 atom の場合は Promise を返すようになりました。

非同期 atom を更新すると Supsense の Suspend が発生するようになる

これまでは非同期 atom から atomWithDefault で作成した atom を更新時には、Suspend は発生しませんでした。

今回の変更によって atomWithDefault で作成した非同期 atom は更新時に Suspend が僅かながらでも発生するケースがあり、更新時に Suspense で囲っている境界でちらつきが発生するようになります。

Suspend への対策

Discussion 上では非同期 atom の Suspend への対策として 3 つの例が例示されています。
※2 番目の例は atomWithDefault を使わない例になるので割愛します

Patterns for updating “sync” atoms that depend on async atoms like in V1 · pmndrs/jotai · Discussion #2003

unstable_unwrap

unstable がついていますが将来的に外れる予定の unwrap 関数を使用して非同期 atom を同期 atom に変換します。

この際に注意しないといけないのが、useAtom から返される atom の値が undefined が含まれるようになります。

const asyncAtom = atom(Promise.resolve(1));
const asyncCountAtom = atomWithDefault((get) => get(asyncAtom));
const countAtom = unstable_unwrap(asyncCountAtom);

function Component() {
  const [count, setCount] = useAtom(countAtom);
  //     ^? const count: number | undefined
}

undefined を回避するには、unwrap 関数の第二引数にフォールバックを指定します。

const asyncAtom = atom(Promise.resolve(1));
const asyncCountAtom = atomWithDefault((get) => get(asyncAtom));
const countAtom = unstable_unwrap(asyncCountAtom, (prev) => prev ?? 1);

function Component() {
  const [count, setCount] = useAtom(countAtom);
  //     ^? const count: number
}

初期値が外部リソースではない時はこのケースが適していそうです。

useAtom のdelay オプション

const [count, setCount] = useAtom(countAtom, { delay: 100 });

この辺りが一番手軽に導入できそうですが、遅らせる時間をどれくらいにするのかは悩みどころです。

その他:React の useTransition

const [, startTransition] = useTransition();
const [count, setCount] = useAtom(countAtom);

const increment = () => {
  startTransition(() => {
    setCount((p) => p.then((prev) => prev + 1));
  });
};

Suspense の Suspend によってちらつくなら、そもそも React の機能なので React の API で対応しちゃうパターンです。

さいごに

Jotai の非同期 atom を使うことで、Render-as-you-fetch パターンを簡単に導入できるので非常に便利です。

便利な分、ライブラリのアップデートに定期的に追随していく筋力が必要だなと思う今日この頃です。

この記事を書いた人

美髭公
美髭公事業開発部 web application engineer
2013年にアーティスに入社。システムエンジニアとしてアーティスCMSを使用したWebサイトや受託システムの構築・保守に携わる。環境構築が好き。
この記事のカテゴリ

FOLLOW US

最新の情報をお届けします