Claude Skills Guide

Claude Code Qwik Store Reactive State Management Guide

Qwik’s unique approach to reactivity sets it apart from traditional JavaScript frameworks. Instead of hydrating entire applications on the client, Qwik uses resumability—serializing state into the HTML and resuming execution where the server left off. Understanding how to manage this reactive state is essential for building performant Qwik applications.

This guide covers Qwik’s core state management primitives: useStore for reactive objects and useSignal for primitive values, along with patterns for building scalable state management in your Qwik projects.

Understanding Qwik’s Reactivity Model

Unlike React’s virtual DOM diffing or Vue’s proxy-based reactivity, Qwik uses a fine-grained reactive system that tracks dependencies at the component level. When you modify reactive state, Qwik only updates the specific DOM nodes that depend on that changed value.

The key primitives you’ll work with are:

import { component$, useStore, useSignal, useComputed$ } from '@builder.io/qwik';

export default component$(() => {
  // Primitive state - use useSignal
  const count = useSignal(0);
  
  // Complex state - use useStore
  const user = useStore({
    name: 'John',
    preferences: {
      theme: 'dark',
      notifications: true
    }
  });
  
  // Derived state
  const doubled = useComputed$(() => count.value * 2);
  
  return (
    <div>
      <p>Count: {count.value}</p>
      <p>Doubled: {doubled.value}</p>
      <p>User: {user.name}</p>
      <button onClick$={() => count.value++}>Increment</button>
    </div>
  );
});

useStore Deep Dive

The useStore hook creates a reactive object that Qwik tracks at a fine-grained level. The second argument lets you configure reactivity options.

Basic Store Usage

interface Todo {
  id: number;
  text: string;
  completed: boolean;
}

export const TodoList = component$(() => {
  const store = useStore({
    todos: [] as Todo[],
    filter: 'all' as 'all' | 'active' | 'completed'
  });
  
  return (
    <div>
      <select 
        value={store.filter}
        onChange$={(e) => store.filter = (e.target as HTMLSelectElement).value as any}
      >
        <option value="all">All</option>
        <option value="active">Active</option>
        <option value="completed">Completed</option>
      </select>
      
      <ul>
        {store.todos.map(todo => (
          <li key={todo.id}>
            <input 
              type="checkbox" 
              checked={todo.completed}
              onChange$={(e) => todo.completed = (e.target as HTMLInputElement).checked}
            />
            {todo.text}
          </li>
        ))}
      </ul>
    </div>
  );
});

Nested Reactivity with useStore

One of Qwik’s powerful features is deep reactivity. By default, changes to nested properties trigger updates:

export const NestedStore = component$(() => {
  const store = useStore({
    config: {
      api: {
        endpoint: '/api/v1',
        timeout: 5000
      }
    }
  });
  
  return (
    <div>
      <button onClick$={() => {
        // This deeply nested change still triggers reactivity
        store.config.api.timeout = 10000;
      }}>
        Update Timeout
      </button>
    </div>
  );
});

However, you can optimize performance by using the reactive option set to false for flat structures:

const flatStore = useStore({
  prop1: 'value1',
  prop2: 'value2'
}, { reactive: false });

useSignal for Primitive Values

Use useSignal when you have single primitive values. Signals are more performant than stores for simple values because they have less overhead.

export const SignalCounter = component$(() => {
  const count = useSignal(0);
  const name = useSignal('Guest');
  const isLoading = useSignal(false);
  
  return (
    <div>
      <h1>Hello, {name.value}!</h1>
      <p>Count: {count.value}</p>
      
      <button 
        onClick$={() => count.value++}
        disabled={isLoading.value}
      >
        Increment
      </button>
      
      <button onClick$={() => isLoading.value = !isLoading.value}>
        Toggle Loading
      </button>
    </div>
  );
});

Sharing State Across Components

Qwik provides several patterns for sharing state between components.

Props Drilling with Signals

Pass signals down as props for parent-child communication:

interface Props {
  count: { value: number };
}

export const Parent = component$(() => {
  const sharedCount = useSignal(0);
  
  return (
    <div>
      <Child count={sharedCount} />
      <button onClick$={() => sharedCount.value++}>
        Parent Increment
      </button>
    </div>
  );
});

export const Child = component$<Props>(({ count }) => {
  return (
    <div>
      <p>Child sees: {count.value}</p>
      <button onClick$={() => count.value++}>
        Child Increment
      </button>
    </div>
  );
});

Using Context for Global State

For truly global state, use Qwik’s context API:

import { createContextId, useContext, useContextProvider, useStore } from '@builder.io/qwik';

interface AppState {
  theme: 'light' | 'dark';
  user: { name: string } | null;
}

export const AppContext = createContextId<AppState>('app-context');

export const AppProvider = component$(() => {
  const appState = useStore<AppState>({
    theme: 'light',
    user: null
  });
  
  useContextProvider(AppContext, appState);
  
  return <Slot />;
});

// Using the context in any component
export const ThemedComponent = component$(() => {
  const appState = useContext(AppContext);
  
  return (
    <button onClick$={() => {
      appState.theme = appState.theme === 'light' ? 'dark' : 'light';
    }}>
      Current: {appState.theme}
    </button>
  );
});

Best Practices for Qwik State Management

Follow these practices to build maintainable Qwik applications:

  1. Choose the right primitive: Use useSignal for primitives, useStore for objects. This optimization matters in large applications.

  2. Minimize reactivity scope: When possible, use { reactive: false } on stores that don’t need deep tracking.

  3. Keep state co-located: Define state in the component that needs it. Only lift state up when truly necessary.

  4. Use computed values wisely: useComputed$ caches results and only recalculates when dependencies change, making it efficient for derived state.

  5. Leverage $ suffix: Remember that event handlers ending with $ are lazy-loaded. This is fundamental to Qwik’s performance model.

// Good: Derived value with useComputed$
const fullName = useComputed$(() => 
  `${user.value.firstName} ${user.value.lastName}`
);

// Avoid: Computing derived values during render
// const fullName = user.value.firstName + ' ' + user.value.lastName;

Conclusion

Qwik’s reactive state management offers a fresh perspective on building web applications. By understanding when to use useSignal versus useStore, using context for global state, and following best practices for reactivity, you can build applications that are both highly performant and easy to maintain.

The key insight is that Qwik’s fine-grained reactivity means you don’t need to think about memoization or optimization strategies that plague other frameworks—Qwik handles this automatically at the framework level.

Built by theluckystrike — More at zovo.one