Molecules
Molecules are the core building block of bunshi. They are functions that return a value.
import { molecule } from "bunshi";
export const RandomMolecule = molecule(() => Math.random());
When this
RandomMolecule
is used, it will always return the same random number. The value is memoized and cached.
Molecules can depend on other molecules. When molecules depend on other molecules, anything that they depend on will be automatically created.
import { molecule } from "bunshi";
export const RandomMolecule = molecule(() => Math.random());
export const UsernameMolecule = molecule(
(mol) => `You are user ${mol(RandomMolecule)}`,
);
export const IDMolecule = molecule((mol) => `ID: ${mol(RandomMolecule)}`);
Molecules can also depend on scopes. When a molecule depends on a scope, then an instance will be created for each scope. In other words, your molecule function will be run once per unique scope, instead of once globally for your application.
import { molecule } from "bunshi";
import { userIdScope } from "./scopes";
export const UsernameMolecule = molecule(
(mol, scope) => `You are user ${scope(userIdScope)}`,
);
export const IDMolecule = molecule((mol) => `ID: ${scope(userIdScope)}`);
use
Syntax
New in Bunshi 2.1 is the use
syntax for declaring dependencies instead of calling mol
of scope
.
import { molecule, use } from "bunshi";
export const RandomMolecule = molecule(() => Math.random());
export const UsernameMolecule = molecule(
() => `You are user ${use(RandomMolecule)}`,
);
export const IDMolecule = molecule(() => `ID: ${use(RandomMolecule)}`);
Molecules can also depend on scopes. When a molecule depends on a scope, then an instance will be created for each scope. In other words, your molecule function will be run once per unique scope, instead of once globally for your application.
import { molecule, use } from "bunshi";
import { userIdScope } from "./scopes";
export const UsernameMolecule = molecule(
() => `You are user ${use(userIdScope)}`,
);
export const IDMolecule = molecule(() => `ID: ${use(userIdScope)}`);
Rules
- A molecule without any dependencies or scopes will return a single value.
- A molecule that depends on scope (i.e. a scoped molecule) will return a single value per unique scope.
- A molecule that depends on a Scoped Molecule will return a single value per unique scope of it’s dependency.
- If a molecule calls
scope
oruse
with a scope then it will be a scoped molecule - If a molecule calls
mol
oruse
with a molecule then it will depend on that molecule
Molecule Lifecycle
New in Bunshi 2.1 are the onMount
and onUnmount
lifecycle hooks. They allow state libraries to be shared and
cleaned up at the right time.
onMount
Run some code only when a molecule is mounted.
import { molecule, onMount } from "bunshi";
molecule(() => {
let i = 0;
// use `onMount` to guarantee that your code is run only when a molecule is used
onMount(() => {
const id = setInterval(() => console.log("Ticking...", i++), 1000);
// Calls
return () => clearInterval(id);
});
return i;
});
onUnmount
import { molecule, onUnmount } from "bunshi";
molecule(() => {
onUnmount(() => console.log("Goodbye!"));
return Math.random();
});
Example
Lifecycle events are called differently in React Strict Mode.
See:
Best Practices
Molecules don’t just have to return one thing. For example, if you’re using jotai
, you would normally return a whole set of atoms (i.e. a molecule).
import { molecule } from "bunshi";
import { atom } from "jotai";
export const FormAtom = molecule((mol, scope) => {
const dataAtom = atom({});
const errorsAtom = atom([]);
const hasErrors = atom((get) => get(errorsAtom).length > 0);
// Molecules can return a set of atoms
return {
dataAtom,
errorsAtom,
hasErrors,
};
});
Molecules don’t just have to return only one type of thing. They can create stores using one or many libraries and wire them all together.
Here’s an example that combines three libraries:
- Valtio
- Zustand
- Jotai
The final derivedAtom
is reactive to all 3, and the CountersAtom
is useable across
Vue, React and vanilla javascript.
import { molecule, ComponentScope } from "bunshi";
// Import Zustand
import create from "zustand/vanilla";
// Import Valtio
import { proxy } from "valtio/vanilla";
// Import Jotai
import { atom } from "jotai";
// Adapters for wiring them together
import { atomWithStore } from "jotai-zustand";
import { atomWithProxy } from "jotai-valtio";
export const CountersAtom = molecule(() => {
// Zustand store
const counterStore = create(() => ({ count: 0 }));
// Valtio proxy
const counterProxy = proxy({ count: 0 });
// Jotai atom
const counterAtom = atom(0);
// Derived atom that uses all 3 values
const storeAtom = atomWithStore(counterStore);
const proxyAtom = atomWithProxy(proxyState);
const derivedAtom = atom(
(get) => get(storeAtom) * get(proxyAtom) * get(counterAtom),
);
// Molecules returns all the stores
return {
counterStore,
counterProxy,
counterAtom,
derivedAtom,
};
});