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:

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`')
}