Hooks & createAtom
One of the most important features of Particule is the ability to create custom helper atom
functions that can add new functionality using hooks:
There's currently two helper atoms in the core, that you can use as examples:
resetAtom
to create a resetable atomlocalStorageAtom
to save and fetch the atom value to thelocalStorage
automatically
createAtom
This is the function used to create new helper atoms. It takes an object which is the hooks this helper atom is subcribed to. Here's an example of an helper atom that won't accept a value of 0
:
import { createAtom } from 'particule';
const noZeroAtom = createAtom({
beforeValueSet: (_, value) => {
if (value === 0) {
throw new Error('Cannot set value to 0')
}
return value
}
})
As always, use it the same as any atom:
const counterAtom = noZeroAtom(3)
function App() {
const [count, setCount] = useAtom(counterAtom)
return (
<>
<p>{count}</p>
<button onClick={() => setCount(count => count - 1)}>Reduce</button>
</>
)
}
List of hooks
beforeValueSet
Signature
(atom: Atom<T>, atomValue: T, firstSet: boolean) => T
Called before a new value is set to the atom. If the hook returns a value, it will be used as the new value. atomValue
represents the new value to be set, and firstSet
is only true
when the first set is done (a.k.a when the atom is initialized).
afterValueSet
Signature
(atom: Atom<T>, atomValue: T, firstSet: boolean) => void
Called after a new value has been set to the atom. atomValue
represents the new value that has been set, and firstSet
is only true
when the first set is done (a.k.a when the atom is initialized).
onCreate
Signature
(atom: Atom<T>) => Atom<T>
Called when the atom is created. If the hook returns a value, it will be used as the atom's itself.
Storing data
You can store data to an atom using the UNSAFE_storage
property. In the background, a WeakMap
is used.
For each value stored in this storage, you should create a constant representing the key to access the data, with the type StorageKey
:
import { StorageKey } from 'particule'
export const RESET_KEY: StorageKey = {
key: 'initialValue'
}
Then, you can add/get/remove data to the atom using this key:
const resetAtom = createAtom({
afterValueSet: (atom, value, firstSet) => {
if (firstSet) {
atom.UNSAFE_storage.set(RESET_KEY, value);
}
}
})
Adding new methods to the atom
You can also add new methods to an atom, by extending its type. Start by creating it, and remember that it should extends the Atom
type:
export type ResetAtom<T = unknown> = Atom<T> & {
reset: () => void
}
Then, type the createAtom
function with this new atom type. It will be automatically inferred in the hooks, so you'll be able to set implement the method:
const resetAtom = createAtom<ResetAtom>({
afterValueSet: (atom, value, firstSet) => {
/// ...
},
onCreate: atom => {
atom.reset = () => {
atom.UNSAFE_directSet(atom.UNSAFE_storage.get(RESET_KEY))
atom.UNSAFE_notify()
}
return atom
}
})
You can then create a custom React hook to use this new method:
export const useResetAtom = <T>(atom: Atom<T>): ResetAtomFn => {
const isResetAtom = (value: Atom<T>): value is ResetAtom<T> => 'reset' in value
if (isResetAtom(atom)) {
return atom.reset
}
throw new Error('`useResetAtom` can only be used with atoms from `resetAtom`')
}