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 (<><textstyle={{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 hereuseHydrateAtoms(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 commmon 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 logicfunction Component() {const testAtomValue = useAtomValue(testAtom)return <div>{testAtomValue}</div>}function AtomsHydrator({atomValues,children,}: {// eslint-disable-next-line @typescript-eslint/no-explicit-anyatomValues: Iterable<readonly [WritableAtom<unknown, [any], unknown>, unknown]>children: ReactNode}) {useHydrateAtoms(new Map(atomValues))return children}