This doc describes about jotai core behavior. For async behavior, refer ./async.md.
atom
is a function to create an atom config. It's an object and the object identity is important. It can be created from anywhere and once created, you shouldn't modify the object. (Note: There might be an advanced use case to mutate atom configs after creation. At the moment, it's not officially supported though.)
const primitiveAtom = atom(initialValue)
const derivedAtomWithRead = atom(readFunction)
const derivedAtomWithReadWrite = atom(readFunction, writeFunction)
const derivedAtomWithWriteOnly = atom(null, writeFunction)
There are two kinds of atoms: a writable atom and a read-only atom.
Primitive atoms are always writable. Derived atoms are writable if writeFunction
is specified.
The writeFunction
of primitive atoms is equivalent to the setState of React.useState.
The signature of readFunction
is (get) => Value | Promise<Value>
, and get
is a function that takes an atom config and returns its value stored in Provider described below.
Dependency is tracked, so if get
is used for an atom at least once, the readFunction will be reevaluated whenever the atom value is changed.
The signature of writeFunction is (get, set, update) => void | Promise<void>
.
get
is similar to the one described above, but it doesn't track the dependency. set
is a function that takes an atom config and a new value which then updates the atom value in Provider. update
is an arbitrary value that we receive from the updating function returned by useAtom
described below.
Atom configs don't hold values. Atom values are stored in a Provider. A Provider can be used like React context provider. Usually, we place one Provider at the root of the app, however you could use multiple Providers, each storing different atom values for its component tree.
const Root = () => (
<Provider>
<App />
</Provider>
)
A Provider accepts an optional prop initialValues
which you can specify
some initial atom values.
The use cases of this are testing and server side rendering.
const TestRoot = () => (
<Provider initialValues=[[atom1, 1], [atom2, 'b']]>
<Component />
</Provider>
)
The useAtom hook is to read an atom value stored in the Provider. It returns the atom value and an updating function as a tuple, just like useState. It takes an atom config created with atom()
. Initially, there is no value stored in the Provider. The first time the atom is used via useAtom
, it will add an initial value in the Provider. If the atom is a derived atom, the read function is executed to compute an initial value. When an atom is no longer used, meaning all the components using it is unmounted, and the atom config no longer exists, the value is removed from the Provider.
const [value, updateValue] = useAtom(anAtom)
The updateValue
takes just one argument, which will be passed to the third argument of writeFunction of the atom. The behavior totally depends on how the writeFunction is implemented.
This will allow using accross multiple roots.
You get a bridge value with useBridge
in the outer component
and pass it to Bridge
in the inner component.
const Component = ({ children }) => {
const brigeValue = useBridge()
return (
<AnotherRerender>
<Bridge value={bridgeValue}>
{children}
</Bridge>
</AnotherRerender>
)
}
A working example: https://codesandbox.io/s/jotai-r3f-fri9d
To begin with, let's explain this. In the current implementation, every time we invoke the "read" function, we refresh dependencies. For example, If A depends on B, it means that B is a dependency of A, and A is a dependent of B.
const uppercaseAtom = atom(get => get(textAtom).toUpperCase())
The read function is the first parameter of the atom. The dependency will initially be empty. On first use, we run the read function and know that uppercaseAtom depends on textAtom. textAtom is the dependency of uppercaseAtom. So, add uppercaseAtom to the dependents of textAtom. When we re-run the read function (because its dependency (=textAtom) is updated), the dependency is built again, which is the same in this case. We then remove stale dependents and replace with the latest one.
Basic examples in readme only show defining atoms globally outside components. There is no restrictions about when we create an atom. As long as we know atoms are identified by their object referential identity, it's okay to create them at anytime.
If you create atoms in render functions, you would typically want to use
some hooks like useRef
or useMemo
.
You can create an atom and store it wth useState
or even in another atom.
See an example in issue #5.
You can cache atoms somewhere globally. See this example or that example.
Check atomFamily in utils too.
- If you create a primitive atom, it will use predefined read/write functions to emulate
useState
behavior. - If you create an atom with read/write functions, they can provide any behavior with some restrictions as follows.
read
function will be invoked during React render phase, so the function has to be pure. What is pure in React is described here.write
function will be invoked where you called initially and in useEffect for following invocations. So, you shouldn't callwrite
in render.- When an atom is initially used with
useAtom
, it will invokeread
function to get the initial value, this is recursive process. If an atom value exists in Provider, it will be used instead of invokingread
function. - Once an atom is used (and stored in Provider), it's value is only updated if its dependencies are updated (including updating directly with useAtom).