본문으로 건너뛰기

Jotai provider

· 약 7분
arch-spatula

Jotai로 순회할 때 각각의 atom이 독립적인 context를 가져야 할 때 사용할 수 있는 전략입니다.

<li/> 컴포넌트에서 Jotai atom 복제하는 방법

  • Jotai의 atom은 근본적으로 1개입니다. 렉시컬 환경을 활용해서 여러개로 복제하는 것은 단순한 atom으로 불가능합니다.

시도

ChatGPT

import { atom, useAtom } from 'jotai';

// 독립적인 atom을 생성하는 함수
const createIndependentAtom = (initialValue) => atom(initialValue);

// 예시 컴포넌트
const ExampleComponent = () => {
// 독립적인 atom을 생성
const independentAtom = createIndependentAtom('initial value');
// atom의 상태와 업데이트 함수를 가져옴
const [value, setValue] = useAtom(independentAtom);

const handleClick = () => {
// atom의 상태 업데이트
setValue('new value');
};

return (
<div>
<p>Atom value: {value}</p>
<button onClick={handleClick}>Update Atom</button>
</div>
);
};

export default ExampleComponent;

이런 응답을 받았습니다.

const stableAtom = atom(0);
const Component = () => {
const [atomValue] = useAtom(atom(0)); // This will cause an infinite loop
const [atomValue] = useAtom(stableAtom); // This is fine
const [derivedAtomValue] = useAtom(
useMemo(
// This is also fine
() => atom((get) => get(stableAtom) * 2),
[]
)
);
};

Jotai 공식문서를 보면 무한 리랜더링을 발생시킨다고 합니다.

기대와 결과가 일치하고 무한 리랜더링이 발생했습니다.

구글 검색: jotai list component

Large objects - Jotai 공식문서

공식문서에서 특수한 레시피를 알려줍니다.

일단 동작하는

const initialData = {
people: [
{
name: 'Luke Skywalker',
information: { height: 172 },
siblings: ['John Skywalker', 'Doe Skywalker'],
},
{
name: 'C-3PO',
information: { height: 167 },
siblings: ['John Doe', 'Doe John'],
},
],
films: [
{
title: 'A New Hope',
planets: ['Tatooine', 'Alderaan'],
},
{
title: 'The Empire Strikes Back',
planets: ['Hoth'],
},
],
info: {
tags: ['People', 'Films', 'Planets', 'Titles'],
},
};

하지만 이것은 자세히 보니까 atom을 참조형으로 정의하고 소비하는 것이었습니다.

useMemo

useMemo를 통해서 각각의 컴포넌트마다 렉시컬 환경을 활용하는 방법이 있습니다. 모듈 스코프에서는 하나의 state가 되지만 순회하는 컴포넌트 내부에서 선언하면 동적으로 선언할 수 있다고 합니다.

Note about creating an atom in render function - Jotai 공식문서

const Component = ({ value }) => {
const valueAtom = useMemo(() => atom({ value }), [value]);
// ...
};

useMemo, useRef 2가지 모두 활용할 수 있지만 공식 문서는 useMemo를 활용하고 있기 때문에 저도 useMemo를 활용해보겠습니다.

export function Card({ question, answer, _id, stackCount }: Card) {
// ... 생략
const activeAtom = useMemo(() => atom(false), []);
const [active, setActive] = useAtom(activeAtom);

const editingAtom = useMemo(() => atom(false), []);
const [isEditing, setIsEditing] = useAtom(editingAtom);
// ... 생략

일단 다리를 바꾸니까 성공적으로 동작했습니다.

atom을 렉시컬 환경에서 자원공유가 가능해졌습니다.

다음 단계입니다. 이 atom을 공유하는 방법입니다. 처음에는 custom hook을 활용할까? 생각했습니다. 틀린 생각입니다. custom hook을 활용하면 custom hook을 호출할 때 렉시컬 환경을 활용해서 새롭게 atom이 만들어지기 때문에 부적합니다.

렉시컬 환경 단위로 atom을 공유해야 합니다.

다음 생각은 props로 공유하면 card 컴포넌트의 렉시컬 환경을 활용해서 card 내 같은 atom을 공유할 것이라는 생각이 들었습니다. 하지만 아직 생각 짧습니다. provider를 활용하면 card에서 모두 읽고 쓰기가 가능하다는 생각이 들었습니다.

하지만 또 문제가 있습니다. atom은 함수 안에서 정의되는데 어떻게 provider로 공유할 것인가?

해결: provider 활용

Isolate State in an Application with Jotai Provider - Daishi Kato

jotai-tutorial-10 - Daishi Kato

Jotai를 가르치는 튜토리얼입니다. 여기서 provider는 원래 전역상태로 관리하는데 이렇게하면 각각 독립적인 컨텍스트를 갖게 됩니다.

이것을 이해해보면 순회하는 위치에서 provider를 적용하면 된다는 것입니다.

function Cards() {
// ... 생략
return (
<div>
{/* ... 생략 */}
<CardContainer>
{cards.map((card) => (
// 여기는 provider가 없습니다.
<Card {...card} key={card._id} />
))}
</CardContainer>
{/* ... 생략 */}
</div>
);
}

export default Cards;

현재 상태입니다.

provider가 현재 없습니다.

import { Provider } from 'jotai';

function Cards() {
// ... 생략
return (
<div>
{/* ... 생략 */}
<CardContainer>
{cards.map((card) => (
// 여기는 Jotai provider로 감쌉니다.
<Provider>
<Card {...card} key={card._id} />
</Provider>
))}
</CardContainer>
{/* ... 생략 */}
</div>
);
}

export default Cards;

Jotai provider를 적용하면 동일한 atom을 읽어도 독립적인 context를 갖을 수 있습니다.

또 무조건 atom을 공유할 필요는 없습니다.

const activeAtom = atom(false);
const editingAtom = atom(false);

export function Card({ question, answer, _id, stackCount }: Card) {
const [active, setActive] = useAtom(activeAtom);
const [isEditing, setIsEditing] = useAtom(editingAtom);

// ... 생략

무조건 상위 혹은 custom hook에서 전역으로 공유받을 필요는 없습니다. 하위 모듈의 atom을 주입받을 수 있습니다.

provider 없음

위는 provider가 없습니다.

provider 있음

위는 provider로 감싸져있는 경우입니다.

학습

  • jotai provider를 제공하면 atom은 독립적인 context를 갖을 수 있게 됩니다.