Overview
useSignal provides React hooks for integrating Signal reactive primitives with React components. These hooks enable fine-grained reactivity in React applications.
React Peer Dependency RequiredThis module requires React 16.8+ as a peer dependency. Install with:The module exports are commented out by default and need React to be available in your project.
Import
import {
useSignal,
useComputed,
useSignalValue,
useSignalEffect
} from "bytekit";
// or
import { useSignal } from "bytekit/use-signal";
useSignal
Create a Signal that persists across component renders.
function useSignal<T>(initialValue: T): Signal<T>
Usage
import { useSignal } from "bytekit";
function Counter() {
const count = useSignal(0);
return (
<div>
<p>Count: {count.value}</p>
<button onClick={() => count.value++}>Increment</button>
</div>
);
}
useComputed
Create a computed Signal that automatically updates when dependencies change.
function useComputed<T>(compute: () => T): Computed<T>
Usage
import { useSignal, useComputed } from "bytekit";
function TodoStats() {
const todos = useSignal<Todo[]>([]);
const completedCount = useComputed(() =>
todos.value.filter(t => t.completed).length
);
return (
<div>
<p>Completed: {completedCount.value} / {todos.value.length}</p>
</div>
);
}
useSignalValue
Subscribe to a Signal and trigger re-render when it changes.
function useSignalValue<T>(sig: Signal<T>): T
Usage
import { signal } from "bytekit";
import { useSignalValue } from "bytekit/use-signal";
// Global signal outside component
const theme = signal<"light" | "dark">("light");
function ThemeToggle() {
const currentTheme = useSignalValue(theme);
return (
<button onClick={() => theme.value = currentTheme === "light" ? "dark" : "light"}>
Current: {currentTheme}
</button>
);
}
useSignalEffect
Run an effect that automatically tracks Signal dependencies.
function useSignalEffect(callback: () => void | (() => void)): void
Usage
import { useSignal, useSignalEffect } from "bytekit";
function Logger() {
const count = useSignal(0);
useSignalEffect(() => {
console.log(`Count changed to ${count.value}`);
// Optional cleanup
return () => {
console.log("Effect cleanup");
};
});
return <button onClick={() => count.value++}>Increment</button>;
}
useSignalFromProps
Create a Signal that syncs with React props.
function useSignalFromProps<T>(value: T): Signal<T>
Usage
import { useSignalFromProps } from "bytekit";
function ControlledInput({ value }: { value: string }) {
const text = useSignalFromProps(value);
return (
<input
value={text.value}
onChange={e => text.value = e.target.value}
/>
);
}
useBatch
Batch multiple Signal updates to trigger only one re-render.
function useBatch(): (callback: () => void) => void
Usage
import { useSignal, useBatch } from "bytekit";
function MultiUpdate() {
const count = useSignal(0);
const name = useSignal("John");
const batch = useBatch();
const updateBoth = () => {
batch(() => {
count.value++;
name.value = "Jane";
// Only triggers one re-render
});
};
return <button onClick={updateBoth}>Update Both</button>;
}
Complete Example
import {
useSignal,
useComputed,
useSignalEffect,
useBatch
} from "bytekit";
interface Todo {
id: string;
text: string;
completed: boolean;
}
function TodoApp() {
const todos = useSignal<Todo[]>([]);
const filter = useSignal<"all" | "active" | "completed">("all");
const batch = useBatch();
const filteredTodos = useComputed(() => {
const list = todos.value;
switch (filter.value) {
case "active": return list.filter(t => !t.completed);
case "completed": return list.filter(t => t.completed);
default: return list;
}
});
const stats = useComputed(() => ({
total: todos.value.length,
completed: todos.value.filter(t => t.completed).length,
active: todos.value.filter(t => !t.completed).length
}));
useSignalEffect(() => {
console.log("Todos changed:", todos.value.length);
});
const addTodo = (text: string) => {
todos.value = [...todos.value, {
id: Math.random().toString(36),
text,
completed: false
}];
};
const toggleTodo = (id: string) => {
todos.value = todos.value.map(t =>
t.id === id ? { ...t, completed: !t.completed } : t
);
};
const clearCompleted = () => {
batch(() => {
todos.value = todos.value.filter(t => !t.completed);
filter.value = "all";
});
};
return (
<div>
<h1>Todo App</h1>
<p>
{stats.value.active} active, {stats.value.completed} completed
</p>
<select
value={filter.value}
onChange={e => filter.value = e.target.value as any}
>
<option value="all">All</option>
<option value="active">Active</option>
<option value="completed">Completed</option>
</select>
<ul>
{filteredTodos.value.map(todo => (
<li key={todo.id}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => toggleTodo(todo.id)}
/>
{todo.text}
</li>
))}
</ul>
<button onClick={clearCompleted}>Clear Completed</button>
</div>
);
}
Optimize Renders
- Use
useComputed for derived values to avoid recalculation
- Use
useBatch when updating multiple Signals at once
- Keep Signal updates granular for fine-grained reactivity
React Strict ModeSignals work correctly with React Strict Mode’s double-rendering behavior.
See Also