Skip to main content
Module

x/jotai/docs/api/core.mdx

👻 Primitive and flexible state management for React
Go to Latest
File
---title: Coredescription: This doc describes core `jotai` bundle.nav: 2.01---
- [Basic APIs](/docs/api/core.mdx#basic-apis)- [Additional APIs](/docs/api/core.mdx#additional-apis)## Basic APIs
## atom
The `atom` function is to create an atom config.We call it "atom config" as it's just a definition and it doesn't yet hold a value.We may also call it just "atom" if the context is clear.
An atom config is an immutable object. The atom config object doesn't hold a value. The atom value exists in a Provider store.
To create a primitive atom (config), all you need is to provide an initial value.
```jsimport { atom } from 'jotai'
const priceAtom = atom(10)const messageAtom = atom('hello')const productAtom = atom({ id: 12, name: 'good stuff' })```
You can also create derived atoms. We have three patterns:
- Read-only atom- Write-only atom- Read-Write atomTo create derived atoms, we pass a read function and an optional write function.
```jsconst readOnlyAtom = atom((get) => get(priceAtom) * 2)const writeOnlyAtom = atom( null, // it's a convention to pass `null` for the first argument (get, set, update) => { // `update` is any single value we receive for updating this atom set(priceAtom, get(priceAtom) - update.discount) })const readWriteAtom = atom( (get) => get(priceAtom) * 2, (get, set, newPrice) => { set(priceAtom, newPrice / 2) // you can set as many atoms as you want at the same time })```
`get` in the read function is to read the atom value.It's reactive and read dependencies are tracked.
`get` in the write function is also to read atom value, but it's not tracked.Furthermore, it can't read unresolved async values.For async behavior, please refer to the [async](../guides/async.mdx) doc.
`set` in the write function is to write atom value.It will invoke the write function of the target atom.
**Note**: Atom configs can be created anywhere, but referential equality is important.They can be created dynamically too.To create an atom in render function, `useMemo` or `useRef` is required to get a stable reference. If in doubt about using `useMemo` or `useRef` for memoization, use `useMemo`.
```jsconst Component = ({ value }) => { const valueAtom = useMemo(() => atom({ value }), [value]) // ...}```
### Signatures
```ts// primitive atomfunction atom<Value>(initialValue: Value): PrimitiveAtom<Value>
// read-only atomfunction atom<Value>(read: (get: Getter) => Value | Promise<Value>): Atom<Value>
// writable derived atomfunction atom<Value, Update>( read: (get: Getter) => Value | Promise<Value>, write: (get: Getter, set: Setter, update: Update) => void | Promise<void>): WritableAtom<Value, Update>
// write-only derived atomfunction atom<Value, Update>( read: Value, write: (get: Getter, set: Setter, update: Update) => void | Promise<void>): WritableAtom<Value, Update>```
- `initialValue`: the initial value that the atom will return until its value is changed.- `read`: a function that's called on every re-render. The signature of `read` is `(get) => Value | Promise<Value>`, and `get` is a function that takes an atom config and returns its value stored in Provider as described below. Dependency is tracked, so if `get` is used for an atom at least once, the `read` will be reevaluated whenever the atom value is changed.- `write`: a function mostly used for mutating atom's values, for a better description; it gets called whenever we call the second value of the returned pair of `useAtom`, the `useAtom()[1]`. The default value of this function in the primitive atom will change the value of that atom. The signature of `write` 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.```jsconst primitiveAtom = atom(initialValue)const derivedAtomWithRead = atom(read)const derivedAtomWithReadWrite = atom(read, write)const derivedAtomWithWriteOnly = atom(null, write)```
There are two kinds of atoms: a writable atom and a read-only atom. Primitive atoms are always writable. Derived atoms are writable if the `write` is specified. The `write` of primitive atoms is equivalent to the `setState` of `React.useState`.
### `debugLabel` property
The created atom config can have an optional property `debugLabel`. The debug label is used to display the atom in debugging. See [Debugging guide](../guides/debugging.mdx) for more information.
Note: While, the debug labels don’t have to be unique, it’s generally recommended to make them distinguishable.
### `onMount` property
The created atom config can have an optional property `onMount`. `onMount` is a function which takes a function `setAtom` and returns `onUnmount` function optionally.
The `onMount` function is called when the atom is first used in a provider, and `onUnmount` is called when it’s no longer used. In some edge cases, an atom can be unmounted and then mounted immediately.
```jsconst anAtom = atom(1)anAtom.onMount = (setAtom) => { console.log('atom is mounted in provider') setAtom(c => c + 1) // increment count on mount return () => { ... } // return optional onUnmount function}```
Calling `setAtom` function will invoke the atom’s `write`. Customizing `write` allows changing the behavior.
```jsconst countAtom = atom(1)const derivedAtom = atom( (get) => get(countAtom), (get, set, action) => { if (action.type === 'init') { set(countAtom, 10) } else if (action.type === 'inc') { set(countAtom, (c) => c + 1) } })derivedAtom.onMount = (setAtom) => { setAtom({ type: 'init' })}```
## useAtom
The `useAtom` hook is to read an atom value in the state.The state can be seen as a WeakMap of atom configs and atom values.
The `useAtom` hook returns the atom value and an update function as a tuple,just like React's `useState`.It takes an atom config created with `atom()`.
Initially, there is no value associated with the atom.Only once the atom is used via `useAtom`,does the initial value get stored in the state.If the atom is a derived atom, the read function is called to compute the initial value.When an atom is no longer used, meaning all the components using it are unmounted,and the atom config no longer exists, the value in the state is garbage collected.
```jsconst [value, updateValue] = useAtom(anAtom)```
The `updateValue` takes just one argument, which will be passedto the third argument of the write function of the atom.The behavior depends on how the write function is implemented.
**Note:** as mentioned in the _atom_ section, you have to take care of handling the reference of your atom, otherwise it may enter an infinite loop
```jsconst 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), [] ) )}```
**Note**: Remember that React is responsible for calling your component. Meaning it has to be idempotent, ready to be called multiple times. You will often see an extra re-render even if no props or atoms have changed. An extra re-render without a commit is an expected behavior. It is actually the default behavior of useReducer in React 18.
### Signatures
```ts// primitive or writable derived atomfunction useAtom<Value, Update>( atom: WritableAtom<Value, Update>, scope?: Scope): [Value, SetAtom<Update>]
// read-only atomfunction useAtom<Value>(atom: Atom<Value>, scope?: Scope): [Value, never]```
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 are unmounted, and the atom config no longer exists, the value is removed from the Provider.
```jsconst [value, updateValue] = useAtom(anAtom)```
The `updateValue` takes one argument, which will be passed to the third argument of writeFunction of the atom. The behavior depends on how the writeFunction is implemented.
---
## Notes
### How atom dependency works
To begin with, let's explain this. In the current implementation, every time we invoke the "read" function, we refresh the dependencies and dependents. For example, If A depends on B, it means that B is a dependency of A, and A is a dependent of B.
```jsconst 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` has a dependency on `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 created again, which is the same in this case. We then remove stale dependents and replace with the latest one.
### Atoms can be created on demand
While the basic examples here show defining atoms globally outside components,there's no restrictions about where or when we can create an atom.As long as we remember that atoms are identified by their object referential identity,we can create them anytime.
If you create atoms in render functions, you would typically want to usea hook like `useRef` or `useMemo` for memoization. If not, the atom would be re-created each time the component renders.
You can create an atom and store it with `useState` or even in another atom.See an example in [issue #5](https://github.com/pmndrs/jotai/issues/5).
You can cache atoms somewhere globally.See [this example](https://twitter.com/dai_shi/status/1317653548314718208) or[that example](https://github.com/pmndrs/jotai/issues/119#issuecomment-706046321).
Check [`atomFamily`](../api/utils.mdx#atom-family) in utils for parameterized atoms.
## Additional APIs
## Provider
The `Provider` component is to provide state for a component sub tree.Multiple Providers can be used for multiple subtrees, and they can even be nested.This works just like React Context.
If an atom is used in a tree without a Provider,it will use the default state. This is so-called provider-less mode.
Providers are useful for three reasons:
1. To provide a different state for each sub tree.2. To accept initial values of atoms.3. To clear all atoms by remounting.
```jsxconst SubTree = () => ( <Provider> <Child /> </Provider>)```
### Signatures
```tsconst Provider: React.FC<{ initialValues?: Iterable<readonly [AnyAtom, unknown]> scope?: Scope}>```
Atom configs don't hold values. Atom values reside in separate stores. A Provider is a component that contains a store and provides atom values under the component tree. A Provider works like React context provider. If you don't use a Provider, it works as provider-less mode with a default store. A Provider will be necessary if we need to hold different atom values for different component trees. Provider also has some capabilities described below, which doesn't exist in the provider-less mode.
```jsxconst Root = () => ( <Provider> <App /> </Provider>)```
### `initialValues` prop
A Provider accepts an optional prop `initialValues`, with which you can specifysome initial atom values.The use cases of this are testing and server side rendering.
#### Example
```jsxconst TestRoot = () => ( <Provider initialValues={[ [atom1, 1], [atom2, 'b'], ]}> <Component /> </Provider>)```
#### TypeScript
The `initialValues` prop is not type friendly.We can mitigate it by using a helper function.
```tsconst createInitialValues = () => { const initialValues: (readonly [Atom<unknown>, unknown])[] = [] const get = () => initialValues const set = <Value>(anAtom: Atom<Value>, value: Value) => { initialValues.push([anAtom, value]) } return { get, set }}```
### `scope` prop
A Provider accepts an optional prop `scope` that you can use for a scoped Provider.When using atoms with a scope, the provider with the same scope is used.The recommendation for the scope value is a unique symbol.The primary use case of scope is for library usage.
#### Example
```jsxconst myScope = Symbol()
const anAtom = atom('')
const LibraryComponent = () => { const [value, setValue] = useAtom(anAtom, myScope) // ...}
const LibraryRoot = ({ children }) => ( <Provider scope={myScope}>{children}</Provider>)```
## useSetAtom
```jsxconst switchAtom = atom(false)
const SetTrueButton = () => { const setCount = useSetAtom(switchAtom) const setTrue = () => setCount(true) return ( <div> <button onClick={setTrue}>Set True</button> </div> )}
const SetFalseButton = () => { const setCount = useSetAtom(switchAtom) const setFalse = () => setCount(false) return ( <div> <button onClick={setFalse}>Set False</button> </div> )}
export default function App() { const state = useAtomValue(switchAtom) return ( <div> State: <b>{state.toString()}</b> <SetTrueButton /> <SetFalseButton /> </div> )}```
In case you need to update a value of an atom without reading it, you can use `useSetAtom()`.
This is especially useful when the performance is a concern, as the `const [, setValue] = useAtom(valueAtom)` will cause unnecessary rerenders on each `valueAtom` update.
## useAtomValue
```jsxconst countAtom = atom(0)
const Counter = () => { const setCount = useSetAtom(countAtom) const count = useAtomValue(countAtom) return ( <> <div>count: {count}</div> <button onClick={() => setCount(count + 1)}>+1</button> </> )}```
Similar to the `useSetAtom` hook, `useAtomValue` allows you to access a read-only atom.
### Some more notes about atoms
- 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](https://gist.github.com/sebmarkbage/75f0838967cd003cd7f9ab938eb1958f).- `write` function will be invoked where you called initially and in useEffect for following invocations. So, you shouldn't call `write` in render.- When an atom is initially used with `useAtom`, it will invoke `read` function to get the initial value, this is recursive process. If an atom value exists in Provider, it will be used instead of invoking `read` 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).