Reactive state management using signals and stores for fine-grained reactivity and optimal performance. Qwik's state system is designed to work seamlessly with the resumable architecture.
Reactive primitive for managing single values with automatic change detection.
/**
* Create reactive signal within a component
* @param initialValue - Initial value for the signal
* @returns Signal object with reactive value
*/
function useSignal<T>(initialValue?: T): Signal<T>;
/**
* Create signal outside of component context
* @param initialValue - Initial value for the signal
* @returns Signal object
*/
function createSignal<T>(initialValue?: T): Signal<T>;
/**
* Check if an object is a signal
* @param obj - Object to check
* @returns True if object is a signal
*/
function isSignal(obj: any): obj is Signal<any>;
// Signal interface
interface Signal<T> {
value: T;
}
// Read-only signal interface
interface ReadonlySignal<T> {
readonly value: T;
}
// Signal creation options
interface UseSignal<T> {
initialValue?: T;
}Usage Examples:
import { component$, useSignal, createSignal } from "@builder.io/qwik";
// Basic signal usage
export const Counter = component$(() => {
const count = useSignal(0);
return (
<div>
<p>Count: {count.value}</p>
<button onClick$={() => count.value++}>+</button>
<button onClick$={() => count.value--}>-</button>
</div>
);
});
// Signal with complex types
interface User {
name: string;
email: string;
}
export const UserProfile = component$(() => {
const user = useSignal<User>({
name: "John",
email: "john@example.com"
});
return (
<div>
<h1>{user.value.name}</h1>
<p>{user.value.email}</p>
<button onClick$={() => {
user.value = { ...user.value, name: "Jane" };
}}>
Change Name
</button>
</div>
);
});
// Global signal (outside component)
const globalCounter = createSignal(0);
export const GlobalCounterDisplay = component$(() => {
return <div>Global: {globalCounter.value}</div>;
});Reactive objects for managing complex state with deep reactivity.
/**
* Create reactive store for complex state objects
* @param initialState - Initial state object
* @param opts - Store configuration options
* @returns Proxied reactive store
*/
function useStore<T>(initialState: T, opts?: UseStoreOptions): T;
// Store configuration options
interface UseStoreOptions {
/** Enable deep reactivity for nested objects */
deep?: boolean;
/** Custom serialization behavior */
reactive?: boolean;
}Usage Examples:
import { component$, useStore } from "@builder.io/qwik";
// Basic store usage
export const TodoApp = component$(() => {
const state = useStore({
todos: [] as Array<{ id: number; text: string; completed: boolean }>,
filter: "all" as "all" | "active" | "completed",
nextId: 1,
});
const addTodo = $((text: string) => {
state.todos.push({
id: state.nextId++,
text,
completed: false,
});
});
const toggleTodo = $((id: number) => {
const todo = state.todos.find(t => t.id === id);
if (todo) {
todo.completed = !todo.completed;
}
});
return (
<div>
<h1>Todos ({state.todos.length})</h1>
<input
onKeyDown$={(e) => {
if (e.key === "Enter") {
addTodo((e.target as HTMLInputElement).value);
(e.target as HTMLInputElement).value = "";
}
}}
placeholder="Add todo..."
/>
<ul>
{state.todos.map(todo => (
<li key={todo.id}>
<input
type="checkbox"
checked={todo.completed}
onChange$={() => toggleTodo(todo.id)}
/>
{todo.text}
</li>
))}
</ul>
</div>
);
});
// Deep reactivity store
export const NestedStateExample = component$(() => {
const state = useStore({
user: {
profile: {
name: "John",
settings: {
theme: "dark",
notifications: true,
},
},
},
}, { deep: true });
return (
<div>
<h1>Hello {state.user.profile.name}</h1>
<p>Theme: {state.user.profile.settings.theme}</p>
<button onClick$={() => {
// Deep mutation is reactive
state.user.profile.settings.theme =
state.user.profile.settings.theme === "dark" ? "light" : "dark";
}}>
Toggle Theme
</button>
</div>
);
});Derived reactive values that update automatically when dependencies change.
/**
* Create computed signal within component
* @param compute - Computation function
* @returns Computed signal
*/
function useComputed$<T>(compute: ComputedFn<T>): Signal<T>;
/**
* Create computed signal from QRL
* @param compute - QRL-wrapped computation function
* @returns Computed signal
*/
function useComputedQrl<T>(compute: QRL<ComputedFn<T>>): Signal<T>;
/**
* Create computed signal outside component
* @param compute - Computation function
* @returns Computed signal
*/
function createComputed$<T>(compute: ComputedFn<T>): Signal<T>;
/**
* Create computed signal from QRL outside component
* @param compute - QRL-wrapped computation function
* @returns Computed signal
*/
function createComputedQrl<T>(compute: QRL<ComputedFn<T>>): Signal<T>;
// Computation function type
type ComputedFn<T> = () => T;Usage Examples:
import { component$, useSignal, useComputed$ } from "@builder.io/qwik";
export const ShoppingCart = component$(() => {
const items = useStore([
{ name: "Apple", price: 1.50, quantity: 2 },
{ name: "Banana", price: 0.75, quantity: 3 },
]);
// Computed total that updates automatically
const total = useComputed$(() => {
return items.reduce((sum, item) => {
return sum + (item.price * item.quantity);
}, 0);
});
// Computed item count
const itemCount = useComputed$(() => {
return items.reduce((sum, item) => sum + item.quantity, 0);
});
return (
<div>
<h2>Shopping Cart ({itemCount.value} items)</h2>
<ul>
{items.map((item, index) => (
<li key={index}>
{item.name} - ${item.price} x {item.quantity}
<button onClick$={() => items[index].quantity++}>+</button>
<button onClick$={() => items[index].quantity--}>-</button>
</li>
))}
</ul>
<p><strong>Total: ${total.value.toFixed(2)}</strong></p>
</div>
);
});Control reactive tracking and subscriptions.
/**
* Execute function without tracking reactive dependencies
* @param fn - Function to execute untracked
* @returns Result of function execution
*/
function untrack<T>(fn: () => T): T;Usage Examples:
import { component$, useSignal, useComputed$, untrack } from "@builder.io/qwik";
export const ConditionalTracking = component$(() => {
const count = useSignal(0);
const enabled = useSignal(true);
// Computed that conditionally tracks count
const conditionalValue = useComputed$(() => {
if (enabled.value) {
return count.value * 2; // Tracks count when enabled
} else {
// Don't track count when disabled
return untrack(() => count.value) + 100;
}
});
return (
<div>
<p>Count: {count.value}</p>
<p>Conditional: {conditionalValue.value}</p>
<button onClick$={() => count.value++}>Increment Count</button>
<button onClick$={() => enabled.value = !enabled.value}>
Toggle Tracking ({enabled.value ? "ON" : "OFF"})
</button>
</div>
);
});Create immutable values that don't change during component lifecycle.
/**
* Create constant value that persists across re-renders
* @param fn - Factory function to create the constant
* @returns Constant value
*/
function useConstant<T>(fn: () => T): T;Usage Examples:
import { component$, useConstant, useSignal } from "@builder.io/qwik";
export const ExpensiveInitialization = component$(() => {
// Expensive initialization that runs only once
const expensiveData = useConstant(() => {
console.log("Computing expensive data...");
return Array.from({ length: 1000 }, (_, i) => ({
id: i,
value: Math.random(),
}));
});
const filter = useSignal("");
const filteredData = useComputed$(() => {
return expensiveData.filter(item =>
item.id.toString().includes(filter.value)
);
});
return (
<div>
<input
value={filter.value}
onInput$={(e) => filter.value = (e.target as HTMLInputElement).value}
placeholder="Filter by ID..."
/>
<p>Showing {filteredData.value.length} items</p>
<ul>
{filteredData.value.slice(0, 10).map(item => (
<li key={item.id}>
ID: {item.id}, Value: {item.value.toFixed(3)}
</li>
))}
</ul>
</div>
);
});Control serialization behavior for state objects.
/**
* Mark object as non-serializable (client-side only)
* @param obj - Object to mark as non-serializable
* @returns Non-serializable wrapper
*/
function noSerialize<T>(obj: T): NoSerialize<T>;
/**
* Unwrap proxy store to access underlying object
* @param obj - Proxied store object
* @returns Underlying object
*/
function unwrapStore<T>(obj: T): T;
// Non-serializable wrapper interface
interface NoSerialize<T> {
readonly __no_serialize__: true;
readonly __value__: T;
}Usage Examples:
import { component$, useStore, noSerialize } from "@builder.io/qwik";
export const ClientOnlyData = component$(() => {
const state = useStore({
// This will be serialized and sent to client
serverData: { message: "Hello from server" },
// This won't be serialized (client-side only)
clientOnlyData: noSerialize({
canvas: null as HTMLCanvasElement | null,
audioContext: null as AudioContext | null,
}),
});
return (
<div>
<p>{state.serverData.message}</p>
<canvas
ref={(el) => {
if (state.clientOnlyData) {
state.clientOnlyData.canvas = el;
}
}}
/>
<button onClick$={() => {
if (state.clientOnlyData) {
state.clientOnlyData.audioContext = new AudioContext();
}
}}>
Initialize Audio
</button>
</div>
);
});