State

State management is an important part of any app. In Qwik, we can differentiate between two types of state, reactive and static.

Static state is anything that can be serialized: a string, number, object, array... anything. Reactive state on the other hand is created with useStore().

It's important to notice that state in Qwik is not component state, but rather app state that can be instantiated by any component.

useStore()

const reactive = useStore(initialState) is a hook that creates a reactive object. It takes an initial state as a parameter, and returns a reactive object.

The reactive object returned by useStore() is just like any other object, but it's reactive. If you change a property of the object, any component that depends on it will be updated.

NOTE Make sure to keep a reference to the reactive object and not only to its properties, for reactivity to work. e.g. doing let { count } = useStore({ count: 0 }) and then mutating count won't trigger updates of components that depends on it.

Example

An example showing how useStore is used in the Counter example to keep track of the count.

const App = component$(() => {
  const state = useStore({ count: 0 });

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

The example above, the App component is using useStore() to create a reactive object. The object is then used to keep track of the count. The count is then displayed in the component.

Just accessing the state.count property will cause the component to be updated if the count changes, when the property is changed in the click handler.

Recursive values

By default, useStore() only tracks the top level fields in your store, which means any for any updates to register, you have to update values at the top level field.

For example, this will not work:

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

export const App = component$(() => {
  const store = useStore({
    nested: { fields: { are: "not tracked" }}
  })
  
  return (
    <>
      <p>{store.nested.fields.are}</p>
      <button onClick$={() => store.nested.fields.are = "tracked"}>Click me</button>
    </>
  );
});

In order to update this with default tracking we would have to update the top level nested field:

store.nested = { fields: { are: { "tracked" } } }

In order to make the first example work, we can pass a second argument to our useStore, and tell it to use recursion to track all the values in our store, no matter how deep.

export const App = component$(() => {
  const store = useStore({
    nested: { fields: { are: "not tracked" }}
  }, { recursion: true })
  
  return (
    <>
      <p>{store.nested.fields.are}</p>
      <button onClick$={() => store.nested.fields.are = "tracked"}>Click me</button>
    </>
  );
});

Now the component will update as expected. This will also track individual values inside of arrays!

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

export const App = component$(() => {
  const store = useStore({
    letters: ["A", "B", "C"]
  }, { recursive: true })
  
  return (
    <>
      {store.letters.map(letter => <p>{letter}</p>)}
      <button onClick$={() => { store.letters[2] = "Z"}}>Click me</button>
    </>
  );
});

Passing a store to other components

One of the nice features of Qwik is that the state can be passed to other components, and both can read and write to it, allowing data to flow across the tree in all directions.

There are three ways to pass state to other components.

Using props

The most naive way to pass state to other components is to pass it as props. This is the way you would do it in React, and it works in Qwik as well.

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

  return (
    <>
      <Child userData={userData} />
    </>
  );
});

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

Using context API

The context API is a way to pass state to components without having to pass it through props. Automatically, all the descendant components in the tree get a reference to the state with read/write access to it.

Check the context API for more information.

const CTX = createContext('stuff');

const Stores = component$(() => {
  const userData = useStore({
    count: 0,
  });

  useContextProvider(CTX, userData);

  return (
    <>
      <Child />
    </>
  );
});

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

Computed values

Computed values are values that are derived from other values. They are useful when you have a value that is derived from other values, and you want to keep it in sync with the other values.

In Qwik there are two ways to create computed values, using useWatch$() or useResource$().

The main difference between the two is that useWatch$() allows side effects and the execution is serialized, while useResource$() is asynchronous and multiple useResource calls can happen in parallel.

useWatch$() is usually used to compute intermediate state, while useResource$() has better ergonomics to compute the final state, used for rendering. Let's see some examples:

useWatch$() example

const App = component$(() => {
  const state = useStore({
    count: 0,
    doubleCount: 0
  });

  useWatch$(({ track }) => {
    track(state, 'count');
    state.doubleCount = state.count * 2;
  });

  return (
    <>
      <button onClick$={() => state.count++}>Increment</button>
      Count: {state.count}
      Count * 2: {state.doubleCount}
    </>
  );
});

useResource$() example

const App = component$(() => {
  const state = useStore({
    count: 0,
  });

  const doubleCount = useResource$(({ track }) => {
    track(state, 'count');
    return state.count * 2;
  });

  return (
    <>
      <button onClick$={() => state.count++}>Increment</button>
      Count: {state.count}
      Count * 2: {doubleCount.promise}
    </>
  );
});

Both useWatch and useResource are explained in detail in their respective sections.

Reactivity

Thanks to the fine grained reactivity of Qwik, only the components that depend on the state will be updated. This is a huge performance gain, as only the components that needs to be updated will be updated.

Made with ❤️ by