Skip to content
Bunshi
GitHub

React

Bunshi ships with support for React out of the box.

import { useMolecule, ScopeProvider, MoleculeProvider } from "bunshi/react";

Basic API

useMolecule

Use a molecule for the current scope. Will produce a different value depending on the React context it is run in.

import { useMolecule } from "bunshi/react";
import { useSetAtom, useAtomValue } from "jotai";

export const PageComponent = () => {
  const pageAtoms = useMolecule(PageMolecule);

  const setParams = useSetAtom(pageAtoms.setParams);
  const page = useAtomValue(pageAtoms.currentPage);

  return (
    <div>
      Page: {page}
      <br />
      <button onClick={() => setParams({ date: Date.now() })}>
        Set current time
      </button>
    </div>
  );
};

By default useMolecule will provide a molecule based off the implicit scope from context. You can override this behaviour by passing options to useMolecule.

  • withScope - will overide a scope value
  • withUniqueScope - will override a scope value with a new unique value
  • exclusiveScope - will override ALL scopes

Instead of a scope provider, you can use an explicit scope when using a molecule. This can simplify integrating with other libraries.

Implicit scope:

const App = () => (
  <ScopeProvider scope={UserScope} value={"sam@example.com"}>
    <UserComponent />
  </ScopeProvider>
);

Explicit scope:

useMolecule(UserMolecule, { withScope: [UserScope, "sam@example.com"] });

ScopeProvider

Provides a new value for Scope, similar to React Context. This will create new molecules in the react tree that depend on it.

import { ScopeProvider } from "bunshi/react";

const App = () => (
  <ScopeProvider scope={UserScope} value={"sam@example.com"}>
    <UserComponent />
  </ScopeProvider>
);
  • scope the MoleculeScope reference to provide
  • value a new value for that scope

Advanced API

MoleculeProvider

Provides implementations for molecule interfaces throughout the React component tree, allowing you to supply concrete molecules for abstract interfaces.

// Define interface and implementation
const SendsEmailMolecule = moleculeInterface<{
  sendEmail(recipient: string);
}>();

const SendGmailMolecule = molecule(() => ({
  sendEmail: (recipient: string) => { /* ... */ },
}));

// Provide implementation
const App = () => (
  <MoleculeProvider interface={SendsEmailMolecule} value={SendGmailMolecule}>
    <EmailForm />
  </MoleculeProvider>
);

Interface Resolution in Child Molecules

Child molecules can resolve interfaces while preserving existing molecule instances:

// Parent molecule (created once, shared everywhere)
const CacheMolecule = molecule(() => new Map());

// Molecule using interfaces
const UserServiceMolecule = molecule((get) => {
  const cache = get(CacheMolecule);       // Existing molecule (not re-created)
  const db = get(DatabaseInterface);      // Resolved via provider
  const logger = get(LoggerInterface);    // Resolved via provider
  return {
    async getUser(id: string) {
      const cached = cache.get(id);
      if (cached) return cached;
      // ... fetch and cache
    }
  };
});

const App = () => (
  <MoleculeProvider interface={DatabaseInterface} value={SqliteDatabaseMolecule}>
    <MoleculeProvider interface={LoggerInterface} value={ConsoleLevelMolecule}>
      <UserProfile userId="123" />
    </MoleculeProvider>
  </MoleculeProvider>
);

Providers create child injectors that inherit the parent cache, preserving existing molecules while enabling interface resolution.