@sigrea/react
v0.4.0
Published
React adapter bindings for Sigrea molecule modules.
Downloads
307
Maintainers
Readme
@sigrea/react
@sigrea/react adapts @sigrea/core molecule modules and signals for use in React components. It binds scope-aware lifecycles to useEffect, synchronizes signal subscriptions with React rendering, and provides hooks for both shallow and deep reactivity.
- Signal subscriptions.
useSignalsubscribes to signals and computed values, triggering re-renders when they change. - Computed subscriptions.
useComputedsubscribes to computed values and memoizes them per component instance. - Deep signal subscriptions.
useDeepSignalsubscribes to deep signal objects and exposes them for direct mutation. - Molecule lifecycles.
useMoleculemounts molecule factories and binds their lifecycles to React components.
Table of Contents
Install
npm install @sigrea/react @sigrea/core react react-domRequires React 18+ and Node.js 20 or later.
Quick Start
Consume a Signal
import { signal } from "@sigrea/core";
import { useSignal } from "@sigrea/react";
const count = signal(0);
export function CounterLabel() {
const value = useSignal(count);
return <span>{value}</span>;
}Bridge Framework-Agnostic Molecules
import { molecule, signal } from "@sigrea/core";
import { useMolecule, useSignal } from "@sigrea/react";
const CounterMolecule = molecule((props: { initialCount: number }) => {
const count = signal(props.initialCount);
const increment = () => {
count.value += 1;
};
const reset = () => {
count.value = props.initialCount;
};
return { count, increment, reset };
});
export function Counter(props: { initialCount: number }) {
const counter = useMolecule(CounterMolecule, props);
const value = useSignal(counter.count);
return (
<div>
<span>{value}</span>
<button onClick={counter.increment}>Increment</button>
<button onClick={counter.reset}>Reset</button>
</div>
);
}Work with Deep Signals
import { deepSignal } from "@sigrea/core";
import { useDeepSignal } from "@sigrea/react";
const form = deepSignal({ name: "Sigrea" });
export function ProfileForm() {
const state = useDeepSignal(form);
return (
<label>
Name
<input
value={state.name}
onChange={(event) => {
state.name = event.target.value;
}}
/>
</label>
);
}API Reference
useSignal
function useSignal<T>(signal: Signal<T> | ReadonlySignal<T>): TSubscribes to a signal or computed value and returns its current value. The component re-renders when the signal changes.
useComputed
function useComputed<T>(source: Computed<T>): TSubscribes to a computed value and returns its current value. The component re-renders when the computed value changes, and the subscription is cleaned up when the component unmounts.
useDeepSignal
function useDeepSignal<T extends object>(signal: DeepSignal<T>): TExposes a deep signal object for direct mutation within the component. Updates to nested properties trigger re-renders, and the subscription is cleaned up when the component unmounts.
useMolecule
function useMolecule<TProps, TReturn>(
molecule: MoleculeFactory<TProps, TReturn>,
props?: TProps
): TReturnMounts a molecule factory and returns its public API. The molecule's scope is bound to the component lifecycle: onMount callbacks run after the component mounts, and onUnmount callbacks run before it unmounts.
Testing
// tests/Counter.test.tsx
import { render, screen, fireEvent } from "@testing-library/react";
import { Counter } from "../components/Counter";
it("increments and displays the updated count", () => {
render(<Counter initialCount={10} />);
const incrementButton = screen.getByText("Increment");
fireEvent.click(incrementButton);
expect(screen.getByText("11")).toBeInTheDocument();
});Handling Scope Cleanup Errors
For global error handling configuration, see @sigrea/core - Handling Scope Cleanup Errors.
In React apps, configure the handler in your application entry point before rendering:
// index.tsx or main.tsx
import { setScopeCleanupErrorHandler } from "@sigrea/core";
import { createRoot } from "react-dom/client";
import { App } from "./App";
setScopeCleanupErrorHandler((error, context) => {
console.error(`Cleanup failed:`, error);
// Forward to monitoring service
if (typeof Sentry !== "undefined") {
Sentry.captureException(error, {
tags: { scopeId: context.scopeId, phase: context.phase },
});
}
});
createRoot(document.getElementById("root")!).render(<App />);Development
This repo targets Node.js 20 or later.
If you use mise:
mise trust -y— trustmise.toml(first run only).mise run ci— run CI-equivalent checks locally.mise run notes— preview release notes (optional).
You can also run pnpm scripts directly:
pnpm install— install dependencies.pnpm test— run the Vitest suite once (no watch).pnpm typecheck— run TypeScript type checking.pnpm test:coverage— collect coverage.pnpm build— compile via unbuild to produce dual CJS/ESM bundles.pnpm cicheck— run CI checks locally.pnpm dev— launch the playground counter demo.
See CONTRIBUTING.md for workflow details.
License
MIT — see LICENSE.
