or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

build-optimization.mdcli.mdcomponents.mdcontext.mdevent-handling.mdindex.mdjsx-elements.mdloader.mdqrl-system.mdserver-rendering.mdstate-management.mdstyling.mdtasks-resources.mdtesting.md
tile.json

state-management.mddocs/

State Management

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.

Capabilities

Signals

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>;
});

Stores

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>
  );
});

Computed Values

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>
  );
});

Tracking Control

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>
  );
});

Constants

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>
  );
});

Serialization Control

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>
  );
});