API reference
signalium/react
Functions
component
export default function component<Props extends object>(
fn: (props: Props) => React.ReactNode | React.ReactNode[] | null,
): (props: Props) => React.ReactElement;
export default function component<Props extends object>(
fn: (props: Props) => Promise<React.ReactNode | React.ReactNode[] | null>,
): (props: Props) => React.ReactElement;
Create a reactive component from a pure function. Inside the function, read Signal values and other reactive sources directly. Re-renders are scheduled automatically when dependencies change.
You may pass an async function only when the Signalium async transform is enabled; it compiles await for Suspense. Wrap those components in <Suspense>. See React integration — Async components with Suspense for the transition-like update model (eager for React state, lazy while async reactives are pending).
import { component, useSignal } from 'signalium/react';
import { reactive, type Signal } from 'signalium';
const fullName = reactive(
(first: Signal<string>, last: Signal<string>) =>
`${first.value} ${last.value}`,
);
const Name = component(() => {
const first = useSignal('Ada');
const last = useSignal('Lovelace');
return (
<div>
<p>{fullName(first, last)}</p>
<button onClick={() => (first.value = 'Grace')}>First → Grace</button>
<button onClick={() => (last.value = 'Hopper')}>Last → Hopper</button>
</div>
);
});
| Parameter | Type | Description |
|---|---|---|
| fn | (props: Props) => ReactNode or async → Promise<ReactNode> (with preset) | Sync or async (compiled) render function |
useSignal
export function useSignal<T>(
value: T,
opts?: {
equals?: (prev: T, next: T) => boolean | false;
id?: string;
desc?: string;
},
): Signal<T>;
Create a component-scoped Signal<T> for local state. The signal is stable across renders and disposed on unmount. Prefer useSignal over useState when you want granular reactivity and direct reads.
import { component, useSignal } from 'signalium/react';
const Counter = component(() => {
const count = useSignal(0);
return (
<div>
<button onClick={() => count.update((v) => v - 1)}>-</button>
<span>{count.value}</span>
<button onClick={() => count.update((v) => v + 1)}>+</button>
</div>
);
});
// Custom equality (always update)
const Search = component(() => {
const query = useSignal('', { equals: () => false });
return (
<input
value={query.value}
onChange={(e) => (query.value = e.target.value)}
/>
);
});
Signals can be passed as parameters to reactive functions, and the reactive function will update less often with signal parameters than with plain values. This is because reactive functions will rerun lazily, from innermost to outermost, and only rerun if the parameters or the signals they access have changed.
import { reactive, type Signal } from 'signalium';
import { component, useSignal } from 'signalium/react';
// Deeply nested reactive graph
const formatName = reactive((first: Signal<string>, last: Signal<string>) => {
return `${first.value} ${last.value}`;
});
const greeting = reactive(
(prefix: Signal<string>, first: Signal<string>, last: Signal<string>) => {
return `${prefix.value} ${formatName(first, last)}`;
},
);
const cardText = reactive(
(
title: Signal<string>,
prefix: Signal<string>,
first: Signal<string>,
last: Signal<string>,
) => {
return `[${title.value}] ${greeting(prefix, first, last)}`;
},
);
const ProfileCard = component(() => {
const title = useSignal('Engineer');
const prefix = useSignal('Hello,');
const first = useSignal('Ada');
const last = useSignal('Lovelace');
// Passing signals means only the minimal inner layers recompute
const text = cardText(title, prefix, first, last);
return (
<div>
<p>{text}</p>
<button onClick={() => (first.value = 'Grace')}>First → Grace</button>
<button onClick={() => (last.value = 'Hopper')}>Last → Hopper</button>
<button onClick={() => (prefix.value = 'Hi,')}>Prefix → Hi,</button>
<button onClick={() => (title.value = 'Captain')}>Title → Captain</button>
</div>
);
});
When first or last change, only formatName (and dependents) recompute. cardText does not rerun unless its own signal params (title, prefix, first, last) change or a nested reactive it calls produces a new value. Passing plain values instead of Signals would force outer layers to rerun more often.
| Parameter | Type | Description |
|---|---|---|
| value | T | Initial value |
| opts.equals | ((prev: T, next: T) => boolean) | false | Equality function |
| opts.id | string | Debug identifier |
| opts.desc | string | Debug description |
useReactive
export function useReactive<R>(signal: Signal<R>): R;
export function useReactive<R>(signal: ReactivePromise<R>): ReactivePromise<R>;
export function useReactive<R, Args extends readonly unknown[]>(
fn: (...args: Args) => R,
...args: Args
): R;
Helper function to read reactive values in standard React components that are not defined with component. This hook is only needed if you are not converting a component to a reactive component, OR if you are using a reactive value inside of a custom hook that is also used in non-reactive components.
Examples:
import { useSignal, useReactive } from 'signalium/react';
import { reactive, task } from 'signalium';
// 1) Read a signal
const Display = () => {
const message = useSignal('hello');
const value = useReactive(message);
return <p>{value}</p>;
};
// 2) Read a computed reactive function
// Here we create a reactive function and use it directly
const computeArea = reactive(
(width: Signal<number>, height: Signal<number>) => width.value * height.value,
);
const Area = () => {
const width = useSignal(3);
const height = useSignal(4);
const area = useReactive(computeArea, width, height);
return (
<div>
<p>Area: {area}</p>
<button onClick={() => (width.value += 1)}>W+1</button>
<button onClick={() => (height.value += 1)}>H+1</button>
</div>
);
};
// 3) Read an async reactive function
const fetchUser = reactive(async (id: string) => {
const res = await fetch(`/api/users/${id}`);
return res.json() as Promise<{ id: string; name: string }>;
});
const User = () => {
let user = useReactive(fetchUser, '1');
return (
<div>
{user.isPending && <p>Loading…</p>}
{user.error && <p>Error</p>}
{user.isReady && <p>{user.value.name}</p>}
</div>
);
};
| Overload | Parameters | Returns |
|---|---|---|
| Signal | signal: Signal<R> | R |
| ReactivePromise | signal: ReactivePromise<R> | ReactivePromise<R> |
| Function | fn: (...args) => R, ...args | R (or ReactivePromise if async) |
useContext
export function useContext<T>(context: Context<T>): T;
Read a context value inside React components. Use with ContextProvider to supply values. Is cross-compatible between reactive and non-reactive components, but should still follow the rules of hooks inside reactive components.
import { component, useContext, ContextProvider } from 'signalium/react';
import { context } from 'signalium';
const Theme = context<'light' | 'dark'>('light');
const Label = component(() => {
const theme = useContext(Theme);
return <span>Theme: {theme}</span>;
});
const App = component(() => (
<ContextProvider contexts={[[Theme, 'dark']]}>
<Label />
</ContextProvider>
));
| Parameter | Type | Description |
|---|---|---|
| context | Context<T> | Context to read |
ContextProvider
export function ContextProvider(props: {
contexts?: [...ContextPair<unknown[]>] | [];
inherit?: boolean;
children: React.ReactNode;
}): React.ReactElement;
Provide contexts to a React subtree using an array of context pairs. A context pair is a 2-tuple of [Context<T>, T]. This component is a flattened alternative to nesting many providers — pass multiple pairs in a single contexts array instead of creating deeply nested providers. Set inherit={false} to create an isolated scope that does not read parent contexts.
import { component, ContextProvider, useContext } from 'signalium/react';
import { context } from 'signalium';
const Theme = context<'light' | 'dark'>('light');
const Lang = context<'en' | 'es'>('en');
const Read = component(() => {
const theme = useContext(Theme);
const lang = useContext(Lang);
return (
<p>
{lang} / {theme}
</p>
);
});
const App = component(() => (
<ContextProvider
contexts={[
[Theme, 'dark'],
[Lang, 'es'],
]}
>
{/* Both Theme and Lang are provided without nesting */}
<Read />
{/* Override Lang only for a subtree */}
<ContextProvider contexts={[[Lang, 'en']]}>
<Read />
</ContextProvider>
{/* Create an isolated scope that ignores parents */}
<ContextProvider
inherit={false}
contexts={[
[Theme, 'light'],
[Lang, 'en'],
]}
>
<Read />
</ContextProvider>
</ContextProvider>
));
| Prop | Type | Description |
|---|---|---|
| contexts | [...ContextPair<unknown[]>] | Contexts to provide |
| inherit | boolean | Inherit parent scope (default true) |
| children | React.ReactNode | Children |
PauseSignalsProvider
export function PauseSignalsProvider(props: {
value: boolean;
children: React.ReactNode;
}): React.ReactElement;
Temporarily pause signal subscriptions for an entire React subtree. When value={true}, signals in the subtree are unwatched — relays are torn down, updates don't trigger re-renders, and last known values are preserved. When value={false}, signals are re-watched and resume normal updates. Toggling value does not re-render descendants (the provider uses a stable context reference internally).
This is particularly useful for React Native applications where screens remain mounted but inactive (e.g., background tabs), or when you need to temporarily pause expensive computations for performance reasons.
import { component, PauseSignalsProvider, useSignal } from 'signalium/react';
import { reactive } from 'signalium';
const expensiveComputation = reactive((input: Signal<number>) => {
// Heavy computation
return input.value * Math.random();
});
const TabNavigator = component(() => {
const [activeTab, setActiveTab] = useState('home');
const data = useSignal(0);
return (
<>
{/* Home tab - active when selected */}
<PauseSignalsProvider value={activeTab !== 'home'}>
<div style={{ display: activeTab === 'home' ? 'block' : 'none' }}>
<HomeTab data={data} />
</div>
</PauseSignalsProvider>
{/* Profile tab - paused when not selected */}
<PauseSignalsProvider value={activeTab !== 'profile'}>
<div style={{ display: activeTab === 'profile' ? 'block' : 'none' }}>
<ProfileTab data={data} />
</div>
</PauseSignalsProvider>
<button onClick={() => setActiveTab('home')}>Home</button>
<button onClick={() => setActiveTab('profile')}>Profile</button>
</>
);
});
// React Native example with navigation
import { useIsFocused } from '@react-navigation/native';
const TabScreen = component(() => {
const isFocused = useIsFocused();
return (
<PauseSignalsProvider value={!isFocused}>
<YourTabContent />
</PauseSignalsProvider>
);
});
Behavior:
- Paused (
value={true}): Signals are unwatched, relays are torn down, updates don't trigger re-renders, last known values are preserved - Active (
value={false}): Normal signal subscription, updates trigger re-renders, signals show current values
Important notes:
- Paused signals will still rerun if the component tree re-renders for other reasons (e.g., prop changes), but they will compute with the last known values for any relays in the paused subtree
- For permanent cleanup, unmount the component normally — pausing is for temporary resource savings
- Works with both
useReactiveandcomponent()which are the primary signal entry points in React
| Prop | Type | Description |
|---|---|---|
| value | boolean | Whether to pause (true) or not |
| children | React.ReactNode | Children to pause/resume |