JotaiJotai

상태
React를 위한 기본적이고 유연한 상태 관리

Initializing state on render

초기 렌더링 시 상태 초기화하기

아톰 상태를 초기 렌더링 시에 초기화하는 방법

아톰 상태를 초기 렌더링 시에 초기화하려면 default 프로퍼티를 사용합니다. 이 프로퍼티는 아톰이 처음 생성될 때 초기값을 설정합니다.

const countAtom = atom({
key: 'count',
default: 0,
});

위 예제에서 countAtom은 초기값으로 0을 가집니다. 이 아톰을 사용하는 컴포넌트가 처음 렌더링될 때, 상태는 0으로 초기화됩니다.

동적 초기값 설정하기

초기값을 동적으로 설정해야 하는 경우, default 프로퍼티에 함수를 전달할 수 있습니다. 이 함수는 아톰이 처음 생성될 때 호출되며, 반환값이 초기값으로 사용됩니다.

const dynamicCountAtom = atom({
key: 'dynamicCount',
default: () => {
const initialValue = localStorage.getItem('count') || 0;
return Number(initialValue);
},
});

이 예제에서는 localStorage에서 값을 가져와 초기값으로 설정합니다. 이 방법을 사용하면 초기값을 런타임에 동적으로 결정할 수 있습니다.

초기 렌더링 시 상태 초기화 주의사항

  • 아톰의 초기값은 컴포넌트의 첫 렌더링 시에만 설정됩니다. 이후 리렌더링에서는 초기값이 다시 설정되지 않습니다.
  • 동적 초기값을 사용할 때는 함수가 순수해야 합니다. 부수 효과(side effect)가 발생하지 않도록 주의해야 합니다.
  • 아톰의 초기값은 모든 컴포넌트에서 공유됩니다. 특정 컴포넌트에서만 다른 초기값을 사용하려면 새로운 아톰을 생성해야 합니다.

이러한 방법들을 활용하면 초기 렌더링 시 아톰 상태를 효과적으로 초기화할 수 있습니다.

여러분은 아톰을 사용하는 재사용 가능한 컴포넌트를 만들어야 할 때가 있습니다.

이 아톰들의 초기 상태는 컴포넌트에 전달된 props에 의해 결정됩니다.

아래는 ProviderinitialValues prop을 사용하여 상태를 초기화하는 기본 예제입니다.

기본 예제

CodeSandbox 링크: codesandbox.

일반 텍스트를 표시하고 업데이트할 수 있는 재사용 가능한 TextDisplay 컴포넌트를 생각해 보세요.

이 컴포넌트에는 PrettyTextUpdateTextInput이라는 두 개의 자식 컴포넌트가 있습니다.

  • PrettyText는 텍스트를 파란색으로 표시합니다.
  • UpdateTextInput은 텍스트 값을 업데이트하는 입력 필드입니다.

두 자식 컴포넌트에 text를 prop으로 전달하는 대신, text 상태를 아톰으로 공유하기로 결정했습니다.

TextDisplay 컴포넌트를 재사용 가능하게 만들기 위해, initialTextValue prop을 받아 text 아톰의 초기 상태를 결정합니다.

initialTextValuetextAtom에 연결하기 위해, 새로운 스토어를 생성하고 이를 Provider 컴포넌트에 전달하는 컴포넌트로 자식 컴포넌트를 감쌉니다.

const textAtom = atom('')
const PrettyText = () => {
const [text] = useAtom(textAtom)
return (
<>
<text
style={{
color: 'blue',
}}
>
{text}
</text>
</>
)
}
const UpdateTextInput = () => {
const [text, setText] = useAtom(textAtom)
const handleInputChange = (e) => {
setText(e.target.value)
}
return (
<>
<input onChange={handleInputChange} value={text} />
</>
)
}
const HydrateAtoms = ({ initialValues, children }) => {
// 렌더링 시 prop으로 초기 상태 설정
useHydrateAtoms(initialValues)
return children
}
export const TextDisplay = ({ initialTextValue }) => (
<Provider>
<HydrateAtoms initialValues={[[textAtom, initialTextValue]]}>
<PrettyText />
<br />
<UpdateTextInput />
</HydrateAtoms>
</Provider>
)

이제 TextDisplay 컴포넌트를 다른 초기 텍스트 값으로 쉽게 재사용할 수 있습니다. 이는 동일한 아톰을 참조하더라도 가능합니다.

export default function App() {
return (
<div className="App">
<TextDisplay initialTextValue="initial text value 1" />
<TextDisplay initialTextValue="initial text value 2" />
</div>
)
}

이러한 동작은 자식 컴포넌트가 가장 가까운 공통 Provider 조상을 찾아 값을 도출하기 때문입니다.

Provider 동작에 대한 자세한 내용은 여기에서 문서를 참조하세요.

더 복잡한 사용 사례는 스코프 확장을 확인하세요.

타입스크립트 사용하기

useHydrateAtoms는 오버로드된 타입을 가지고 있어서, 타입스크립트가 오버로드된 함수에서 타입을 추출할 수 없습니다. 초기 atom 값을 useHydrateAtoms에 전달할 때는 Map을 사용하는 것이 좋습니다.

다음은 작동하는 예제입니다:

import type { ReactNode } from 'react'
import { Provider, atom, useAtomValue } from 'jotai'
import type { WritableAtom } from 'jotai'
import { useHydrateAtoms } from 'jotai/utils'
const testAtom = atom('')
export default function App() {
return (
<Provider>
<AtomsHydrator atomValues={[[testAtom, 'hello']]}>
<Component />
</AtomsHydrator>
</Provider>
)
}
// 이 컴포넌트는 모든 상태와 로직을 포함합니다.
function Component() {
const testAtomValue = useAtomValue(testAtom)
return <div>{testAtomValue}</div>
}
function AtomsHydrator({
atomValues,
children,
}: {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
atomValues: Iterable<
readonly [WritableAtom<unknown, [any], unknown>, unknown]
>
children: ReactNode
}) {
useHydrateAtoms(new Map(atomValues))
return children
}