ReScript bindings for Jotai. Primitive and flexible state management for React.
Versions below 0.3.0 support Jotai v1.
Versions 0.3.0 and higher support Jotai v2. They also require at least rescript 10.1 (for async/await) and react 18+.
Versions 0.4.0 and higher support Jotai v2 with rescript 11 and react 18+.
Install with npm
:
npm install jotai @fattafatta/rescript-jotai
Or install with yarn
:
yarn add jotai @fattafatta/rescript-jotai
Add @fattafatta/rescript-jotai
as a dependency to your bsconfig.json
:
"bs-dependencies": ["@rescript/react", "@fattafatta/rescript-jotai"]
A Provider works just 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. The store property is optional
let store = Jotai.Store.make()
module App = {
@react.component
let make = () =>
<Jotai.Provider store={store}>
...
</Jotai.Provider>
}
Atoms are always stored in a store. If no specific store is passed to Provider
a default store will be created.
Creates a store.
let store = Jotai.Store.make();
Get the default store that is created if no specific store was provided.
let store = Jotai.Store.getDefaultStore();
A store supports 3 functions to access its content.
get
for getting atom values.
let value = Jotai.Store.get(store, atom);
set
for setting atom values.
Jotai.Store.set(store, atom, 1);
sub
for subscribing to atom changes. Returns a function to unsubscribe.
let unsub = Jotai.Store.sub(store, atom, () => {
Js.Console.log2("atom value is changed to", Jotai.Store.get(store, atom))
})
// unsub() to unsubscribe
In Jotai there is no distinction between normal and async atoms (i.e. atoms that hold promise). So in Jotai hooks will always return the resolved value.
It's not possible to reproduce this one to one in ReScript. Therefore, wherever necessary, functions are provided for normal atoms, and separate *Async
functions are provided to handle atoms with promises. (e.g. useAtom
and useAtomAsync
).
CAUTION: Using the wrong function may not result in compile errors, but very likely runtime errors. As a simple guideline just remember that a hook should never return a promise. If the return type is promise<>
use the async version of the function instead.
Atoms have a value, a setter function (from Atom.Actions
), and a set of tags that restrict which operations are allowed on the atom (e.g is the atom #resettable
).
Normally the type will be inferred automatically. If annotation is required it should be sufficient to provide the first type (the value).
Example:
let atom: Jotai.Atom.t<int, _, _> = Jotai.Atom.make(1)
Create a (primitive) readable and writable atom (config).
CAUTION: Don't pass a function as argument to Atom.make
. That would implicitly create a computed atom and the compiler will produce weird types. Use Atom.makeComputed
for that.
let atom1 = Jotai.Atom.make(1)
let atom2 = Jotai.Atom.make(["text"])
// DON'T do this:
let atom3 = Jotai.Atom.make(() => 1)
Create a readonly atom from a function. The function can be async.
let atom1 = Jotai.Atom.makeThunk(async () => 1)
// shorthand for
let atom2 = Jotai.Atom.makeComputed(async ({get}) => 1)
Create a computed readonly atom. A computed atom can combine any number of readable atoms to create a single derived value. The syntax varies slightly from Jotai.
Note the curly braces in ({get})
. Requires React.Suspense
or Utils.Loadable
if the value is a promise (e.g. the getter is async)
let atom1 = Jotai.Atom.make(1)
let atom2 = Jotai.Atom.makeComputed(({get}) => get(atom1) + 1)
let atom3 = Jotai.Atom.makeComputed(({get}) => get(atom1) + get(atom2) + 1)
// using async atoms requires React.Suspense
let atom4 = Jotai.Atom.makeComputed(async ({get}) => await get(asyncAtom) + 1)
This function is no longer necessary. Atoms no fully support async getters out of the box.
(Requires React.Suspense) Create an computed readonly atom with an async getter. All components will be notified when the returned promise resolves.
let atom1 = Jotai.Atom.make(1)
let atom2 = Jotai.Atom.makeComputedAsync(async ({get}) => {atom1->get + 1})
Create a computed atom that supports read and write. The getter may by async, but the setter must be synchronous. For async setters use makeComputedAsync
.
let atom1 = Jotai.Atom.make(1)
let atom2 = Jotai.Atom.makeWritableComputed(
({get}) => get(atom1) + 1,
({get, set}, arg) => {
atom1->set(get(atom1) + arg)
},
)
Create a computed atom with asynchronous write (setter). Jotai supports async write operations for computed atoms. Simply call 'set' when the promise resolves.
let atom1 = Jotai.Atom.make(1)
let atom2 = Jotai.Atom.makeWritableComputedAsync(
({get}) => get(atom1) + 1,
async ({get, set}, arg) => {
// do async stuff
set(atom1, get(atom1) + arg)
},
)
Create a writeOnly computed atom. (Note: Sometimes the type can not be inferred automatically and has to be annotated)
let atom1 = make(1)
let atom2: Jotai.Atom.t<int, _, _> = Jotai.Atom.makeWriteOnly(({get, set}, args) =>
atom1->set(get(atom1) + args)
)
Create a writeOnly computed async atom (i.e. the setter is an async function)
let atom1 = make(1)
let atom2 = Jotai.Atom.makeWriteOnlyAsync(async ({get, set}, args) => atom1->set(get(atom1) + args))
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.
let atom1 = Jotai.Atom.make(1)
atom1->Jotai.Atom.onMount(setAtom => {
setAtom(a => a + 1) // increment count on mount
() => () // return onUnmount function
})
Standard hook to use with read/write synchronous atoms. (For handling of readOnly/writeOnly atoms see Jotai.useAtomValue
or Jotai.useSetAtom
)
let atom1 = Jotai.Atom.make(1)
let (value, setValue) = Jotai.Atom.useAtom(atom1)
Standard hook to use with read/write async atoms (i.e. all atoms that contain a promise). (For handling of readOnly/writeOnly atoms see Jotai.useAtomValueAsync
or Jotai.useSetAtom
)
let atom1 = Jotai.Atom.makeAsync(async () => 1)
let (value, setValue) = Jotai.Atom.useAtomAsync(atom1)
A hook that returns only the update function of an atom. Can be used to access writeOnly atoms.
let atom = Jotai.Atom.make(1)
let setValue = Jotai.Atom.useSetAtom(atom)
setValue(prev => prev + 1)
A hook that returns only the value of a synchronous atom. Can be used to access readOnly atoms.
let atom = Jotai.Atom.make(1)
let value = Jotai.Atom.useAtomValue(atom)
A hook that returns only the value of an async atom (i.e. the atom contains a promise). Can be used to access readOnly async atoms.
let atom = Jotai.Atom.make(1)
let value = Jotai.Atom.useAtomValue(atom)
Creates an atom with a value persisted in localStorage
. Currently only localStorage
is supported.
let atom1 = Jotai.Utils.AtomWithStorage.make('storageKey', 1)
Creates an atom that can be reset to its initialValue with the useResetAtom
hook.
let atom = Jotai.Utils.AtomWithReset.make(1)
// ... change value ...
let reset = Jotai.Utils.useResetAtom(atom)
reset()
Create a resettable, writable atom. Its default value can be specified with a read function instead of an initial value. This function support sync and async getters.
let atom1 = Jotai.Atom.make(1)
let atom2 = Jotai.Utils.AtomWithDefault.make(({get}) => atom1->get + 1)
// async
let atom3 = Jotai.Atom.makeAsync(async () =>1)
let atom4 = Jotai.Utils.AtomWithDefault.make(async({get}) => await atom3->get + 1)
Creates an atom that uses a reducer to update its value.
type actionType = Inc(int) | Dec(int)
let countReducer = (prev, action) => {
switch action {
| Inc(num) => prev + num
| Dec(num) => prev - num
}
}
let atom = Utils.AtomWithReducer.make(0, countReducer)
let (value, dispatch) = Atom.useAtom(atom)
Inc(1)->dispatch
Creates an atomFamily. If the compiler has trouble inferring the type, it is recommended to set the type directly on the function param.
let atomFamily = Jotai.Utils.AtomFamily.make((name: string) => Jotai.Atom.make(name))
let atom = atomFamily("text")
Creates an atomFamily with a supplied comparison function
let atomFamWithEqual = Jotai.Utils.AtomFamily.makeWithEqual(
name => Jotai.Atom.make(name),
(strA, strB) => strA == strB,
)
Removes an atom from an atomFamily.
Jotai.Utils.AtomFamily.remove(atomFamily, "text")
Register a shouldRemove function.
let shouldRemove = (createdAt, param) => param == "test"
Jotai.Utils.AtomFamily.setShouldRemove(atomFamily, shouldRemove)
Unregister the shouldRemove function with Jotai.Utils.AtomFamily.setShouldRemoveUnregister
.
Jotai.Utils.AtomFamily.setShouldRemoveUnregister(atomFamily, Js.Null.empty)
Can be used if you don't want async atoms to suspend or throw to an error boundary.
let atom = Jotai.Atom.makeThunk(async () => "stuff")
let loadableAtom = Jotai.Utils.Loadable.make(atom)
// inside component:
let value = Jotai.Utils.Loadable.useLoadableValue(loadableAtom)
{switch (value.state, value.data, value.error) {
| (#hasData, Some(d), _) => <p>{("Data: " ++ d)->React.string}</p>
| (#hasError, _, Some(e)) => <p>{e->React.string}</p>
| _ => <p>{"Loading..."->React.string}</p>
}}
Creates an atom that is read-only and deeply frozen from an existing atom.
let atom = Jotai.Atom.make(1)
let freeze = Jotai.Utils.FreezeAtom.freezeAtom(atom)
Derives a readonly atom that selects a slice of the state of an atom.
let a = Atom.make(1)
let b = Utils.SelectAtom.make(a, (a, _prev) => a + 1)
Derives a readonly atom that selects a slice of the state of an atom with a custom equality function.
let a = Atom.make(1)
let b = Utils.SelectAtom.makeWithEquality(a, (a, _prev) => a + 1, (a, b) => a == b)
Creates an atom that we can refresh, which is to force reevaluating the read function..
let atom1 = Jotai.Utils.AtomWithRefresh.make(_ => 1)
let (value, refresh) = Jotai.Utils.useRefreshAtom(atom1)
refresh()
Creates a writeable atom that we can refresh, which is to force reevaluating the read function..
let atom1 = Jotai.Utils.AtomWithRefresh.makeComputed(
({get}) => 1,
({get, set}, arg) => {/* set something */},
)
let (value, update) = Jotai.Utils.useAtom(atom1)
Returns a function that can be used to reset a resettable atom.
let atom = Jotai.Utils.AtomWithReset(1) // value: 1
let (_, setValue) = Jotai.Atom.useAtom(atom)
setValue(2) // value: 2
let resetValue = Jotai.Utils.useResetAtom(atom)
resetValue() // value back to: 1
Hook to refresh an AtomWithRefresh
. (This is the equivalent of calling the set function without arguments in Jotai)
let atom1 = Jotai.Utils.AtomWithRefresh.make(_ => 1)
let (value, refresh) = Jotai.Utils.useRefreshAtom(atom1)
refresh()
Allows to use a reducer function with a primitive atom.
type actionType = Inc(int) | Dec(int)
let countReducer = (prev, action) => {
switch action {
| Inc(num) => prev + num
| Dec(num) => prev - num
}
}
let atom = Jotai.Atom.make(0)
let (value, dispatch) = Jotai.Utils.useReducerAtom(atom, countReducer)
Inc(1)->dispatch
Hook to use a loadable atom.
// inside component:
let value = Jotai.Utils.Loadable.useLoadableValue(loadableAtom)
This package was greatly inspired by re-jotai. I just preferred to have a different syntax for the get/set functions.
These functions are not (yet) supported.
- atomWithObservable
- atomWithHash
- selectAtom
- useAtomCallback
- freezeAtom
- splitAtom
- useHydrateAtoms
- options for useAtom
- Vanilla library
- multi argument setters (write functions). This can be accoplished by simly using an array or a record as argument instead.