Jotai
: 일본어로상태
를 뜻한다.Recoil
이라는 리액트팀에서 만든 상태관리 라이브러리에 영감을 받아Zustand
를 만든 개발팀에서 만든상태 관리 라이브러리
이다.hook
패턴을 사용하기에 복잡한 보일러플레이트 없이 사용할 수 있다.
Jotai
은 아주 작은 단위의 상태를 의미하는 atom
단위로 상태를 관리한다. ( = recoil
)
먼저, Provider
로 App
을 감싸준다.
const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement
);
root.render(
<React.StrictMode>
<Provider>
<App />
</Provider>
</React.StrictMode>
);
아래는 Jotai
의 atom
을 선언하는 가장 기본적인 형태의 코드이다.
import { atom } from "jotai";
const nameAtom = atom<string>("");
아래의 코드는 선언한 atom
을 사용하는 코드이다.
import { useAtom, useAtomValue, useSetAtom } from "jotai";
const [name, setName] = useAtom(nameAtom);
// or
const name = useAtomValue(nameAtom);
const setName = useSetAtom(nameAtom);
또한, 아래처럼 recoil
의 selector
와 같은 형태로도 atom
을 정의할수도있다.
// Read-only atom
export const getToUpperNameAtom = atom((get) => {
const name = get(nameAtom);
return name.toUpperCase();
});
// Write-only atom
export const setToUpperNameAtom = atom(null, (get, set, update) => {
set(nameAtom, update.toUpperCase());
});
// Read-Write atom
export const toUpperNameAtom = atom(
(get) => {
const name = get(nameAtom);
return name.toUpperCase();
},
(get, set, update: string) => {
set(nameAtom, update.toUpperCase());
}
);
만약, initialValue
를 동적으로 주고싶다면 아래의 코드를 활용할 수 있다. (근데 사실 useEffect
로 set해주것과 일치)
currentSecondAtom.onMount = (set) => {
set(new Date().getSeconds().toString());
};
atom
선언시 초기값으로의 reset
이 필요한 상황이라면, 아래와 같이 atomWithReset
으로 선언 후 useResetAtom
을 사용할 수 있다.
import { atomWithReset, useResetAtom } from "jotai/utils";
// const nameAtom = atom("");
const nameAtom = atomWithReset("");
const resetName = useResetAtom(nameAtom);
또한 atomWithStorage
를 활용해 atom
을 local storage
나 session storage
에 저장할 수 있는 기능도 제공한다.
import { atom } from "jotai";
import { atomWithStorage } from "jotai/utils";
const darkModeAtom = atomWithStorage("darkMode", false);
const [darkMode, setDarkMode] = useAtom(darkModeAtom);
이외에도 atomWithImmer
등 여러가지 util 관련 hook이 존재한다.
간단한 비동기 처리는 아래와 같이 처리할 수 있다.
import { atom, useAtomValue } from "jotai";
const fetchAtom = atom(async (get) => {
const response = await fetch("https://jsonplaceholder.typicode.com/todos");
return response.json();
});
const fetchData = useAtomValue(fetchAtom);
하지만 이러한 방식의 경우 Suspense
를 같이 사용해주어야한다.
// index.tsx
...
<Suspense fallback={<div>loading...</div>}>
<App />
</Suspense>
...
Suspense
를 쓰지않고 아래의 방식도 가능하다. (오랜만에 Jotai 사용해서 몰랐는데 v2에는 loadable utils로 아래의 방식 제공하는듯...)
// atom.ts
type ResultType = {
loading: boolean;
error: unknown | null;
data: any;
};
const fetchResultAtom = atom<ResultType>({
loading: true,
error: null,
data: null,
});
const fetchAtom = atom(
(get) => get(fetchResultAtom),
(get, set, url: string) => {
const fetchData = async () => {
set(fetchResultAtom, (prev) => ({ ...prev, loading: true }));
try {
const response = await fetch(url);
const data = await response.json();
set(fetchResultAtom, { loading: false, error: null, data });
} catch (error) {
set(fetchResultAtom, { loading: false, error, data: null });
}
};
fetchData();
}
);
fetchAtom.onMount = (set) => {
set("https://jsonplaceholder.typicode.com/todos/1");
};
// index.tsx
import { useAtomValue } from "jotai";
const result = useAtomValue(fetchAtom);
이제 loading
, error
, data
가 잘 나오는 것을 확인할 수 있다.
이외에도 Jotai
에서는 react-query
과 함께 사용하는 atomsWithQuery
, atomsWithMutation
, SSR
시 사용되는 useHydrateAtoms
등 여러 기능들을 제공하기에 필요시 그때그때 찾아서 개발하면 될 것 같다.