JotaiJotai

状態
Primitive and flexible state management for React

Initializing state on render

There are times when you need to create an reusable component which uses atoms.

These atoms' initial state are determined by the props passed to the component.

Below is a basic example illustrating how you can use Provider and its prop, initialValues, to initialize state.

Basic Example

CodeSandbox link: codesandbox.

Consider a basic example where you have a reusable TextDisplay component that allows you to display and update plain text.

This component has two child components, PrettyText and UpdateTextInput.

  • PrettyText displays the text in blue.
  • UpdateTextInput is an input field which updates the text value.

As opposed to passing text as a prop in the two child components, you decided that the text state should be shared between components as an atom.

To make TextDisplay component reusable, we take in a prop initialTextValue, which determines the initial state of the text atom.

To tie initialTextValue to textAtom, we wrap the child components in a component where we create a new store and pass it to a Provider component.

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 }) => {
// initialising on state with prop on render here
useHydrateAtoms(initialValues)
return children
}
export const TextDisplay = ({ initialTextValue }) => (
<Provider>
<HydrateAtoms initialValues={[[textAtom, initialTextValue]]}>
<PrettyText />
<br />
<UpdateTextInput />
</HydrateAtoms>
</Provider>
)

Now, we can easily reuse TextDisplay component with different initial text values despite them referencing the "same" atom.

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

This behavior is due to our child components looking for the lowest common Provider ancestor to derive its value.

For more information on Provider behavior, please read the docs here.

For more complex use cases, check out Scope extension.

Using Typescript

useHydrateAtoms has overloaded types and typescript cannot extract types from overloaded function. It is recommended to use a Map when passing initial atom values to the useHydrateAtoms.

Here is a working example:

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>
)
}
//This component contains all the states and the logic
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
}