Skip to content
Bunshi
GitHub

Quick Start

Installation

Bunshi is distributed on NPM as both a CommonJS and ES Module. It supports both JavaScript and TypeScript, and since it’s written in TypeScript it comes with type definitions and tests for typing out of the box.

npm i -D bunshi

Create a Molecule

Molecules are the core of bunshi. They are functions that return a value. Use molecules to create something to share across your app. For this example, a jotai atom.

import { molecule } from "bunshi";
import { atom } from "jotai/vanilla";

export const CountMolecule = molecule(() => atom(0));

When this CountMolecule is used it will always return the same atom. The value is memoized and cached so that it can be shared across an app.

Use a Molecule

To use a molecule, pass it to useMolecule in your framework integration (React or Vue). An instance of atom will be automatically created once and shared across the application in every call to useMolecule.

import { useMolecule } from "bunshi/react";
import { useAtom } from "jotai";
import React from "react";
import { CountMolecule } from "./molecules";

function Counter() {
  const countAtom = useMolecule(CountMolecule);
  const [count, setCount] = useAtom(countAtom);

  return (
    <div>
      <p>Clicks: {count}</p>
      <button onClick={() => setCount((c) => c + 1)}>Increment</button>
    </div>
  );
}

export default function App() {
  return (
    <div>
      <Counter />
      <Counter />
    </div>
  );
}

Scope a Molecule

Molecules can be scoped. Scoped molecules provide one value per scope, instead of one value globally. In the above example the atom was shared globally across the app, now let’s give each component it’s own state using the built-in ComponentScope.

import { ComponentScope, molecule, use } from "bunshi";
import { atom } from "jotai/vanilla";

export const CountMolecule = molecule(() => {
  // Because scope is used here, this molecule is component scoped
  use(ComponentScope);
  return atom(0);
});

Scoping is declarative. By calling use(ComponentScope) the molecule will automatically be scoped.

import { useMolecule } from "bunshi/react";
import { useAtom } from "jotai";
import React from "react";
import { CountMolecule } from "./molecules";

function Counter() {
  const countAtom = useMolecule(CountMolecule);
  const [count, setCount] = useAtom(countAtom);

  return (
    <div>
      <p>Clicks: {count}</p>
      <button onClick={() => setCount((c) => c + 1)}>Increment</button>
    </div>
  );
}

export default function App() {
  return (
    <div>
      <Counter />
      <Counter />
    </div>
  );
}

Important: See how the source for Counter didn’t change? This shows how Bunshi lets you pull state up, or push state down. No component changes needed, only molecule changes.

Use a custom scope

Sometimes state doesn’t need to be shared globally or per-component, but somewhere in the middle like a Form. Bunshi has createScope for this. There are many use cases for scopes; sharing data across pages, sections, forms, or even rows or columns in a table.

Here let’s share our state for all counters in the same form.

import { createScope, molecule, use } from "bunshi";
import { atom } from "jotai/vanilla";

// FormScope will be used for provided a string value to forms
export const FormScope = createScope("none");

export const CountMolecule = molecule(() => {
  // Since scope is used here, it makes `CountMolecule` scoped to the form
  use(FormScope);
  return atom(0);
});

Provide a custom scope

Custom scopes need to be provided using the framework-specific API. Bunshi comes with integrations into React and Vue to provide scope implicitly.

For React use a ScopeProvider as a wrapping component, and all children can implicitly use that scope.

import { ScopeProvider, useMolecule } from "bunshi/react";
import { useAtom } from "jotai";
import React from "react";
import { CountMolecule, FormScope } from "./molecules";

export default function App() {
  return (
    <div>
      <Form name="One">
        <Counter />
        <Counter />
      </Form>
      <Form name="Two">
        <Counter />
        <Counter />
      </Form>
    </div>
  );
}

const Form: React.FC<{ name: string; children: React.ReactNode }> = ({
  name,
  children,
}) => {
  return (
    <div>
      Form {name}
      <ScopeProvider scope={FormScope} value={name}>
        {children}
      </ScopeProvider>
    </div>
  );
};

function Counter() {
  const countAtom = useMolecule(CountMolecule);
  const [count, setCount] = useAtom(countAtom);

  return (
    <div>
      <p>Clicks: {count}</p>
      <button onClick={() => setCount((c) => c + 1)}>Increment</button>
    </div>
  );
}

In this example everything inside of the <Form> component will share an atom, it won’t be unique per component or shared across the entire application.

Conclusion - Decoupled and Lazy

The code that you wrote is decoupled and lazy.

  • Decoupled: You can refactor the CountMolecule without affecting the thing that depend on it.
  • Lazy: An atom is only created lazily when it’s needed. It’s not eagerly created and sitting around in a global variable.

When your write code with molecules, you can pull state up or push state down, without having to refactor your components.

There are many other powerful features for molecules in Bunshi, such as:

  • Molecules using other molecules
  • Molecules using lifecycles to run code when they are first used (i.e. mounted or unmounted)
  • Molecule interfaces for decoupling molecules
  • Straightforward usage with many different state libraries