5分で理解するJotai

基本系
useState
と同じ要領で利用できる。
けどこれすごいのが、コンポーネントの外でstateを扱えちゃってるよなぁ
import { atom, useAtom } from 'jotai';
const counter = atom(0);
export default function Page() {
const [count, setCounter] = useAtom(counter);
const onClick = () => setCounter(prev => prev + 1);
return (
<div>
<h1>{count}</h1>
<button onClick={onClick}>Click</button>
</div>
)
}

なるほど、コンポーネントの外で状態をモテちゃってるから、グローバルなstateとして扱えるのか。
import { atom, useAtom } from 'jotai';
const theme = atom('light');
export default function Page() {
const [appTheme, setAppTheme] = useAtom(theme);
const handleClick = () => setAppTheme(appTheme === 'light'? 'dark': 'light');
return (
<div className={appTheme}>
<h1>This is a theme switcher</h1>
<button onClick={handleClick}>{appTheme === 'light'? 'DARK': 'LIGHT'}</button>
</div>
)
}

けどstate
として扱うにはuseAtom
しないといけなくて、これはカスタムhookだからコンポーネントの中でしか呼べない。
atom()
自体はこれを返してるらしい。
{ toString: function toString() {}, init: "light", read: function () {}, write: function () {} }
とりあえずuseContext
のあのクソダル定義をしなくていいのは楽でよい。かつ、値の扱いにもいい感じに気が回ってる感じがある。

Read Only Atom
import { atom, useAtom } from 'jotai';
const textAtom = atom('readonly atoms')
const uppercase = atom((get) => get(textAtom).toUpperCase())
export default function Page() {
const [lowercaseText, setLowercaseText] = useAtom(textAtom);
const [uppercaseText] = useAtom(uppercase);
const handleChange = (e) => setLowercaseText(e.target.value);
return (
<div className="app">
<input value={lowercaseText} onChange={handleChange} />
<h1>{uppercaseText}</h1>
</div>
)
}

Write Only atoms
const textAtom = atom('write only atoms')
const uppercase = atom(null, (get, set) => {
set(textAtom, get(textAtom).toUpperCase())
})
直接setXXX
じゃダメなん?という感じだけど、これは
You must be thinking that why we not updating the atoms value directly, why we use a write-only atom to update it's value. Well updating the value using the write-only atom prevents the extra rerenders in our app.
ということで、再レンダリングを抑えられれてパフォーマンス向上になるらしい。
裏側でいい感じにやってくれてるってことでとりあえず納得して次へ進む。

Read Write atoms
ひとつのモジュールの中で、
const count = atom(1);
export const readWriteAtom = atom((get) => get(count),
(get, set) => {
set(count, get(count) + 1);
},
);
こう書くことによって元の値を隠しつつ、アクセサのみを提供できるのでprivete
プロパティ的な状態を表現できている。

Atom Creators
stateをまとめて扱いたい時に作ってたカスタムhook的な感じ。
const createCountIncAtoms = (initialValue) => {
const baseAtom = atom(initialValue)
const valueAtom = atom((get) => get(baseAtom))
const incAtom = atom(null, (get, set) => set(baseAtom, (c) => c + 1))
return [valueAtom, incAtom]
}
共通の振る舞いをするatom
が増えていった場合に、DRYを防止するためのテクニック。

Async Read Atoms
僕の知ってる限りだとswr
っぽく非同期でatom
の初期値を取得するための方法って感じ。
import { loadable } from "jotai/utils"
const countAtom = atom(0);
const asyncAtom = atom(async (get) => get(countAtom));
const loadableAtom = loadable(asyncAtom)
const AsyncComponent = () => {
const [value] = useAtom(loadableAtom)
if (value.state === 'hasError') return <div>{value.error}</div>
if (value.state === 'loading') {
return <div>Loading...</div>
}
return <div>Value: {value.data}</div>
lodableAtom
はこれを返す。
{
state: 'loading' | 'hasData' | 'hasError',
data?: any,
error?: any,
}