---
title: State | Components
keywords: 'useState, state management, reactivity, reactive'
contributors:
  - nnelgxorz
  - saisrikardumpeti
  - voluntadpear
  - kawamataryo
  - JaymanW
  - RATIU5
  - manucorporat
  - literalpie
  - fum4
  - cunzaizhuyi
  - zanettin
  - ChristianAnagnostou
  - shairez
  - forresst
  - almilo
  - Craiqser
  - XiaoChengyin
  - gkatsanos
  - adamdbradley
  - mhevery
  - wtlin1228
  - AnthonyPAlicea
  - sreeisalso
  - wmertens
  - nicvazquez
  - mrhoodz
  - eecopa
  - fabian-hiller
  - julianobrasil
  - aivarsliepa
  - Balastrong
  - Jemsco
  - shairez
  - ianlet
updated_at: '2026-02-10T12:00:00Z'
created_at: '2023-03-20T23:45:13Z'
---

import CodeSandbox from '../../../../../components/code-sandbox/index.tsx';
import { Note, LongNote } from '../../../../../components/note/note';

# State

Applications need to keep track of values that change over time. In Qwik, those values usually fall into two groups:

1. Plain data: serializable values like strings, numbers, objects, and arrays.
2. Reactive state: values that trigger UI updates where they are used. Create it with `useSignal()` or `useStore()`.

State in Qwik does not have to live inside one component. Any component can create state, and that state can be shared with other parts of the app.

## `useSignal()`

A <a href="https://www.builder.io/blog/usesignal-is-the-future-of-web-frameworks" target="_blank" rel="noopener noreferrer">reactive signal</a> is an object with a single `.value` property.

When `.value` changes, Qwik updates the components that use it.

<CodeSandbox src="/src/routes/demo/state/counter-signal/index.tsx">
```tsx {4} /count/
import { component$, useSignal } from '@qwik.dev/core';

export default component$(() => {
  const count = useSignal(0);

  return (
    <button onClick$={() => count.value++}>
      Increment {count.value}
    </button>
  );
});
```
</CodeSandbox>

In this example, `count` is a signal. Clicking the button updates `count.value`, and Qwik updates the button text automatically.

<LongNote title="Passing signals to child components">
Both `.value` and the signal object can be passed as props.

Pass `.value` when the child only needs the current value. Pass the signal object when the child should be able to read and update the value, or when the prop is typed as `Signal<T>`.

**Current value only:**

```tsx
const isClosed = useSignal(false);

return <Child isClosed={isClosed.value} />;
```

This passes the current boolean value.

**Pass the signal object:**

```tsx
const isClosed = useSignal(false);

return <Child isClosed={isClosed} />;
```

This passes the signal object, so the child can read and update `isClosed.value`.
</LongNote>

## `useStore()`

A store is similar to a signal, but for an entire object instead of a single `.value` property.

Pass an initial object, or a function that returns one. Qwik tracks reads and writes to the object's properties. By default, nested objects and arrays are also tracked.

<CodeSandbox src="/src/routes/demo/state/counter-store/index.tsx" >
```tsx {4} /count/#a /name/
import { component$, useStore } from '@qwik.dev/core';

export default component$(() => {
  const state = useStore({ count: 0, name: 'Qwik' });

  return (
    <>
      <button onClick$={() => state.count++}>Increment</button>
      <p>Count: {state.count}</p>
      <input
        value={state.name}
        onInput$={(_, el) => (state.name = el.value)}
      />
      <p>Name: {state.name}</p>
    </>
  );
});
```
</CodeSandbox>

<LongNote title="Avoid destructuring store properties">
Keep the store object, not destructured values.

```tsx
const state = useStore({ count: 0 });

// Good: Qwik can track this property.
state.count++;

// Avoid: `count` is just a local variable now.
const { count } = state;
```

If you destructure a store property, Qwik cannot track writes to that local variable.
</LongNote>

### Nested objects and arrays

By default, a store also tracks objects and arrays inside it:

<CodeSandbox src="/src/routes/demo/state/counter-store-deep/index.tsx" style={{ height: '10em' }}>
```tsx
import { component$, useStore } from '@qwik.dev/core';

export default component$(() => {
  const store = useStore({
    nested: {
      fields: { are: 'also tracked' },
    },
    list: ['Item 1'],
  });

  return (
    <>
      <p>{store.nested.fields.are}</p>
      <button
        onClick$={() => {
          // Updating a nested property updates the UI.
          store.nested.fields.are = 'tracked';
        }}
      >
        Update nested field
      </button>
      <br />
      <button
        onClick$={() => {
          // Updating an array also updates the UI.
          store.list.push(`Item ${store.list.length + 1}`);
        }}
      >
        Add to list
      </button>
      <ul>
        {store.list.map((item, key) => (
          <li key={key}>{item}</li>
        ))}
      </ul>
    </>
  );
});
```
</CodeSandbox>

Nested tracking is convenient, but it can add overhead for large objects. If you only need to track top-level properties, pass `{ deep: false }`:

```tsx
const shallowStore = useStore(
  {
    nested: {
      fields: { are: 'not tracked deeply' }
    },
    count: 0,
  },
  { deep: false }
);
```
### Store methods

Stores can include methods. Use `$()` so the method can be serialized, and use the `function` keyword when the method needs JavaScript's `this` keyword.

```tsx
import { $, component$, useStore, type QRL } from '@qwik.dev/core';

type CountStore = {
  count: number;
  increment: QRL<(this: CountStore) => void>;
};

export default component$(() => {
  const state = useStore<CountStore>({
    count: 0,
    increment: $(function (this: CountStore) {
      this.count++;
    }),
  });

  return (
    <>
      <button onClick$={() => state.increment()}>Increment</button>
      <p>Count: {state.count}</p>
    </>
  );
});
```

<Note title="Why use the function keyword?">
This example uses `this.count` to update the store. Use the `function` keyword here so <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this" target="_blank" rel="noopener noreferrer">`this`</a> points to the store.
</Note>

## Computed values

Sometimes you do not need to store state directly. You can calculate it from existing state, or resolve it from async work.

`useComputed$()` creates a signal for those values. The calculation can be synchronous, such as formatting a name, or asynchronous, such as fetched data or Web Worker output. Qwik automatically tracks every signal and store the function reads and re-runs it when they change (in async functions, reads after the first `await` need [`track()`](#async-computed-values)).

If you do not need a new signal, try [`useTask$()`](/docs/(qwik)/core/tasks/index.mdx#usetask) instead. A task runs code during component setup or when tracked state changes, and you write any result to existing state yourself.

### `useComputed$()`

Create a signal for a value that is calculated from other state.

Qwik keeps the result and runs the function again only when one of the signals it reads changes. The function can be synchronous (shown here) or asynchronous (see [Async computed values](#async-computed-values) below).

<CodeSandbox src="/src/routes/demo/state/computed/index.tsx" style={{ height: '8em' }}>
```tsx {5} /useComputed$/
import { component$, useComputed$, useSignal } from '@qwik.dev/core';

export default component$(() => {
  const name = useSignal('Qwik');
  const upperName = useComputed$(() => {
    // Runs again when name.value changes.
    return name.value.toUpperCase();
  });

  return (
    <>
      <input type="text" bind:value={name} />
      <p>Name: {name.value}</p>
      <p>Uppercase name: {upperName.value}</p>
    </>
  );
});
```
</CodeSandbox>

<Note>
You do not need to list dependencies for `useComputed$()`. Qwik automatically tracks the signals read inside the function.
</Note>

#### Async computed values

The function passed to `useComputed$()` can be `async` (or return a Promise), for values that come from fetching data, calling `server$()`, reading from a Web Worker, or running a long calculation.

Auto-tracking only covers reads before the first `await`: read `signal.value` or `store.prop` at the top of the function and Qwik re-runs it when they change. After the first `await` the tracking context is gone, so reads there are not tracked. To track those, use the `track()` function provided on the context argument:

```tsx
const user = useComputed$(async ({ track }) => {
  // Tracked automatically: read before the first await.
  const id = userId.value;
  const res = await fetch(`/api/users/${id}`);
  // Tracked explicitly: after an await, use track().
  const locale = track(localeSignal);
  return formatUser(await res.json(), locale);
});
```

While the async work is running, the signal is pending. Read `.pending` for the state, and `.value` or `.error` for the result. If you read `.value` before it is ready, that part of the UI can wait and a [`<Suspense>`](/docs/labs/suspense/index.mdx) boundary can show fallback UI.

The async work can run during SSR and in the browser. `useComputed$()` also supports polling for values that need to stay up to date.

<Note title="Migrating from useAsync$">
`useAsync$()`, `createAsync$()`, and the `AsyncSignal` type are deprecated. Use `useComputed$()`, `createComputed$()`, and `ComputedSignal` instead. Synchronous reads before the first `await` are now tracked automatically; `track()` is still provided on the context argument for reads after an `await`.
</Note>

#### Example

Below, `useComputed$()` fetches programming jokes for the current query. Reading `query.value` reruns the function when the query changes, and `abortSignal` cancels any previous request.

<CodeSandbox src="/src/routes/demo/state/async-joke/index.tsx" style={{ height: '20em' }}>
```tsx /useComputed$/ /query.value/ /abortSignal/
import { component$, useComputed$, useSignal } from '@qwik.dev/core';

type Joke = {
  joke?: string;
  setup?: string;
  delivery?: string;
};

export default component$(() => {
  const query = useSignal('');

  const jokes = useComputed$(async ({ abortSignal }) => {
    // Re-run when query.value changes.
    const search = query.value.trim();
    const url = new URL(
      'https://v2.jokeapi.dev/joke/Programming?safe-mode&amount=2'
    );

    if (search) {
      url.searchParams.set('contains', search);
    }

    const response = await fetch(url, { signal: abortSignal });
    const data = (await response.json()) as {
      jokes?: Joke[];
    };

    return data.jokes ?? [];
  });

  let content;
  if (jokes.pending) {
    content = <p>Loading...</p>;
  } else if (jokes.error) {
    content = <div>Error: {jokes.error.message}</div>;
  } else if (jokes.value.length === 0) {
    content = <p>No jokes found</p>;
  } else {
    content = (
      <ul>
        {jokes.value.map((joke, i) => (
          <li key={i}>
            <div style={{ whiteSpace: 'pre-wrap' }}>
              {joke.joke ?? `${joke.setup}\n${joke.delivery}`}
            </div>
          </li>
        ))}
      </ul>
    );
  }

  return (
    <>
      <label>
        Query: <input bind:value={query} />
      </label>
      {content}
    </>
  );
});
```
</CodeSandbox>

The code handles pending and error states inline with `.pending` and `.error`. During SSR, Qwik waits for the async work before rendering the result.

<Note title="Async computed values and Suspense">
The jokes example handles several UI states inline: loading, error, no results, and results.

That is useful when each state needs its own markup. If you only need fallback UI while `.value` is not ready, wrap that part of the UI in [`<Suspense>`](/docs/labs/suspense/index.mdx).
</Note>

<LongNote title="Advanced: channel example">
`useComputed$()` can also keep its own value updated after the first result. For example, a Server-Sent Events connection can write new messages into the signal.

```tsx
import { component$, useComputed$ } from '@qwik.dev/core';

export default component$(() => {
  const state = useComputed$<string>(
    ({ abortSignal }) => {
      const eventSource = new EventSource('/api/state-channel');

      eventSource.onmessage = (event) => {
        state.value = event.data;
      };

      abortSignal.addEventListener('abort', () => {
        eventSource.close();
      });

      return 'init';
    },
    { eagerCleanup: true }
  );

  return <div>Current state is: {state.value}</div>;
});
```

`eagerCleanup` closes the connection as soon as nothing is reading the signal.
</LongNote>

<LongNote title="API reference">
Most apps only need `.value`, `.pending`, and `.error`. The full API also includes options for cleanup, polling, cancellation, timeouts, and manual invalidation.

```tsx
function useComputed$<T>(
  computeFn: (ctx: {
    readonly track: Tracker;
    cleanup: (fn: () => void | Promise<void>) => void;
    readonly abortSignal: AbortSignal;
    readonly previous: T | undefined;
    readonly info: unknown;
  }) => T | Promise<T>,
  options?: {
    initial?: T | (() => T);
    concurrency?: number;
    eagerCleanup?: boolean;
    expires?: number;
    poll?: boolean;
    clientOnly?: boolean;
    timeout?: number;
  }
): ComputedSignal<T>;
```

The function reads reactive state directly (`signal.value`, `store.prop`); Qwik tracks those reads automatically until the first `await`. Context:

- `track`: tracks reads explicitly. Required for reads after the first `await`, where automatic tracking stops.
- `cleanup`: runs cleanup when the function re-runs or the component is destroyed.
- `abortSignal`: aborts when the function re-runs or is destroyed. Pass it to APIs like `fetch()`.
- `previous`: the previous resolved value, if one exists.
- `info`: the value passed to `signal.invalidate(info)`.

Options:

- `initial`: value to use before the async function resolves.
- `concurrency`: number of computations that can run at the same time. Defaults to `1`; `0` means no limit.
- `eagerCleanup`: cleanup as soon as nothing is reading the signal.
- `expires`: time in milliseconds after which the value is stale.
- `poll`: when `expires` is set, re-run automatically once stale. Defaults to `true`.
- `clientOnly`: skip SSR and only run in the browser.
- `timeout`: abort if the function takes longer than this many milliseconds.

Returned signal:

```ts
interface ComputedSignal<T> {
  value: T;
  pending: boolean;
  error: Error | undefined;
  expires: number;
  poll: boolean;
  promise(): Promise<void>;
  abort(reason?: any): void;
  invalidate(info?: unknown): void;
}
```

- `value`: the resolved value, or `initial` if provided.
- `pending`: whether the async function is running.
- `error`: the error if the async function failed.
- `expires` / `poll`: current staleness interval and whether it re-runs automatically.
- `promise()`: starts the async function if needed and waits for it to finish.
- `abort()`: aborts the current async function.
- `invalidate()`: marks the signal to run again.
</LongNote>

## Sharing state

One of the nice features of Qwik is that the state can be passed to other components. Writing to the store will then only re-render the components which read from the store only.

There are two ways to pass state to other components:

1. pass state to child component explicitly using props,
2. or pass state implicitly through context.

### Using props

The simplest way to pass the state to other components is to pass it through props.

<CodeSandbox src="/src/routes/demo/state/passing-props/index.tsx">
```tsx {9} /userData=/
import { component$, useStore } from '@qwik.dev/core';

export default component$(() => {
  const userData = useStore({ count: 0 });
  return <Child userData={userData} />;
});

interface ChildProps {
  userData: { count: number };
}
export const Child = component$<ChildProps>(({ userData }) => {
  return (
    <>
      <button onClick$={() => userData.count++}>Increment</button>
      <p>Count: {userData.count}</p>
    </>
  );
});
```
</CodeSandbox>

### Using context

The context API is a way to pass state to components without having to pass it through props (i.e.: avoids prop drilling issues). Automatically, all the descendant components in the tree can access a reference to the state with read/write access to it.

Check the [context API](../context/index.mdx) for more information.

<CodeSandbox src="/src/routes/demo/state/passing-context/index.tsx">
```tsx /userData/ /export const CTX = createContextId<{ count: number }>('stuff');/ /useContextProvider(CTX, userData);/ /const userData = useContext(CTX);/
import {
  component$,
  createContextId,
  useContext,
  useContextProvider,
  useStore,
} from '@qwik.dev/core';

// Declare a context ID
export const CTX = createContextId<{ count: number }>('stuff');

export default component$(() => {
  const userData = useStore({ count: 0 });

  // Provide the store to the context under the context ID
  useContextProvider(CTX, userData);

  return <Child />;
});

export const Child = component$(() => {
  const userData = useContext(CTX);
  return (
    <>
      <button onClick$={() => userData.count++}>Increment</button>
      <p>Count: {userData.count}</p>
    </>
  );
});
```
</CodeSandbox>

## Serialization

### `useSerializer$()` / `createSerializer$()`

Sometimes you need to serialize data that Qwik doesn't know how to serialize. For this, you can use the `useSerializer$(cfg)` hook to create a serializer for the data. `createSerializer$(cfg)` does the same thing but is not bound to a specific component and can be called anywhere.

The `cfg` argument is an object with generic type `S` (serialized data) and `T` (the object type) with the following properties:

- `deserialize: (data: S) => T`: Creates the object using the initial value or the deserialized value.
- `serialize?: (value: T) => S | Promise<S>`: Optional, serializes the object. If not provided, the object will be serialized as `undefined`.
- `initial?: S`: Optional, the initial value of the serializer.
- `update?: (value: T) => T | void`: Optional, updates the object when the reactive state changes. This can only be passed when `cfg` is a function, see below.

The result is a `SerializerSignal<T>` object. This is a lazy reactive signal that contains the serialized value of the object. It must be passed around as the signal, otherwise serialization will not work.

```tsx /useSerializer$/
import { component$, useSerializer$ } from '@qwik.dev/core';
import { MyClass } from './my-class';

export default component$(() => {
  const serializer = useSerializer$({
    deserialize: (data) => new MyClass(data),
    serialize: (value) => value.serialize(),
    initial: {x: 3, y: 4},
  });

  return <div>{JSON.stringify(serializer.value)}</div>;
});
```

During SSR, `deserialize` will be called with the initial value. You can then use `signal.value` to access the custom object. On the client, the `deserialize` function will be called with the serialized value, but only when the signal is first read. So if you don't need to read the signal, no code will run.

You can also use reactive state to create a serializer. For this, you need to pass the config as a function that captures the reactive state. Then you can use the `update` function to update the object when the reactive state changes. If the `update` function returns an object, it will be used as the new value for the serializer, and it will trigger all listeners. The trigger happens even when returning the same object.

```tsx /useSerializer$/
import { component$, useSerializer$ } from '@qwik.dev/core';
import { ComplexObject } from './complex-object';

export default component$(() => {
  const sig = useSignal(123);
  const serializer = useSerializer$((cfg) => ({
    deserialize: () => new ComplexObject(sig.value),
    update: (obj) => {
      if (sig.value < 7) {
        // ignore changes below 7
        return;
      }
      // Tell the object about the change
      obj.update(sig.value);
      // Return the updated object so the listeners are notified
      return obj;
    }
  }));

  // ...
});
```

This primitive is very powerful, and can be used to create all sorts of custom serializers. Note that the `serialize` function is allowed to return a Promise, so you could for example write the data to a database and return the id. Note that you will need to use `if (isServer)` to guard this operation, so vite won't bundle database code in the client bundle.

Also note that serialization can also happen on the client, when calling `server$` functions.

### `noSerialize()` / `NoSerializeSymbol`

Qwik ensures that all application state is always serializable. This is important to ensure that Qwik applications have a [resumability](/docs/(qwik)/concepts/resumable/index.mdx) property.

Sometimes, it's necessary to store data that can't be serialized; `noSerialize()` instructs Qwik not to attempt serializing the marked value.
For example, a reference to a third-party library such as [Monaco editor](https://microsoft.github.io/monaco-editor/) will always need `noSerialize()`, as it is not serializable.

`NoSerializeSymbol` is an alternative to `noSerialize()`: You can add this symbol to an object (preferably on the prototype) and it will mark the object as non-serializable.

If a value is marked as non-serializable, then that value will not survive serialization events, such as resuming the application on the client from SSR. In this situation, the value will be set to `undefined` and it is up to the developer to re-initialize the value on the client.

#### `useComputed$()` and `noSerialize()`

If a computed value is wrapped with `noSerialize()`, Qwik will not send that value to the client. It will be created again on the client when the computed signal is read.

This is useful for values that cannot be serialized, such as custom class instances.

<CodeSandbox src="/src/routes/demo/state/no-serialize/index.tsx" style={{ height: '8em' }}>
```tsx /noSerialize/
import {
  component$,
  useStore,
  useSignal,
  noSerialize,
  useVisibleTask$,
  type NoSerialize,
} from '@qwik.dev/core';
import type Monaco from './monaco';
import { monacoEditor } from './monaco';

class MyClass {
  [NoSerializeSymbol] = true;
}

export default component$(() => {
  const editorRef = useSignal<HTMLElement>();
  const store = useStore<{ monacoInstance: NoSerialize<Monaco>, myClass: MyClass }>({
    monacoInstance: undefined,
    myClass: undefined,
  });

  useVisibleTask$(() => {
    const editor = monacoEditor.create(editorRef.value!, {
      value: 'Hello, world!',
    });
    // Monaco is not serializable, so we can't serialize it as part of SSR
    // We can however instantiate it on the client after the component is visible
    store.monacoInstance = noSerialize(editor);
    // Here we demonstrate `NoSerializeSymbol` for the same purpose
    store.myClass = new MyClass();
  });
  return <div ref={editorRef}>loading...</div>;
});
```
</CodeSandbox>

### `SerializerSymbol`

The `SerializerSymbol` is a symbol that can be added to an object to achieve custom serialization. There is no corresponding `DeserializerSymbol` however, for that you should use the `useSerializer$()` hook.

This can be used to skip serializing part of an object, or to work with third-party objects that are not serializable by default.

```tsx /SerializerSymbol/
import { component$, SerializerSymbol } from '@qwik.dev/core';
import { getItem, type Item } from './my-db-layer';

const serializer = (o: Item) => o.toJSON();
type MyItem = Item & { [SerializerSymbol]: typeof serializer };

export default component$((props: {id: string}) => {
  const obj = useSignal<MyItem | null>(null);
  useTask$(async () => {
    const item = await getItem(props.id);
    if (item) {
      item[SerializerSymbol] = serializer;
      obj.value = item;
    }
  });
  return <div>{JSON.stringify(obj.value)}</div>;
});
```

You could even monkey-patch the third-party library to add the `SerializerSymbol` to their objects. Note that this can result in problems when the third-party library is updated, so use this method with caution. You will also need to update the types so that our linting plugin doesn't complain about serialization of unknown types.
