Components

Components are basic building blocks of Qwik Applications. Qwik components are unique in that:

  • Qwik components automatically get broken down into lazy-loaded chunks by the Optimizer. (See Optimizer discussion)
  • Are resumable. (A component can get created on a server and continue its execution on the client.) (See resumable discussion)
  • Can render independently of other components on the page. (See rendering discussion)

component$()

A component is a small, reusable piece of code that can be used to build a UI.

In Qwik, they are declared using the component$ method:

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

export const MyCmp = component$((props: MyCmpProps) => {
  // Declare local state
  const state = useStore({
    count: 0,
  });

  // Returns JSX
  return (
    <>
      <span>
        Hello, {props.name} {state.count}
      </span>
      <div>Times: {state.count}</div>
      <button
        onClick$={() => {
          // This will update the local state and cause a re-render.
          // Reactivity is at Qwik's core!
          state.count++;
        }}
      >
        Increment
      </button>
    </>
  );
});

NOTE

  • For an explanation of $ see Lazy-loading and Optimizer discussion.
  • For a detailed discussion of props, see Component/props discussion.

Props

Props are used to pass data into the component. Props are declared as named arguments of the component.

In this example a component Item declares optional name, quantity, description, and price.

interface ItemProps {
   name?: string;
   quantity?: number;
   description?: string;
   price?: number;
}

export const Item = component$((props: ItemProps) => {
  return ...;
});

Using components

Qwik components can use other components.

export const Counter = component$((props: {step?:number, initial?: number}) => {
  ...
});

export const MyApp = component$(() => {
  return (
    <>
      <div>Single: <Counter /></div>
      <div>Dozens: <Counter step={12}/></div>
    </>
  );
});

The above example shows how the MyApp component can use the Counter component. The second example shows how one can use binding to pass values to the Counter component's props.

Re-rendering on Reactivity

Qwik components are reactive on the component level. Component props, as well as stores, are proxies. These proxies track reads as well as writes.

  • A proxy-read during OnRender method execution lets Qwik know that the OnRender method depends on a given property. A read creates a subscription on that property. In our example, OnRender reads{store.count}, which creates a subscription that tells Qwik that whenever the store.count changes, the component should be invalidated.
  • A proxy-write notifies Qwik that all associated subscriptions should be invalidated.

When components get invalidated, they are added to the invalidation queue, which is flushed (rendered) on the next requestAnimationFrame. This acts as a form of coalescing for component rendering.

For a detailed discussion of reactivity, see related discussion.

Storing a reference

Qwik provides the ability to store a reference to any component. To do so, you have to create a signal and pass the signal as ref attribute to the component. After the component was mounted, the reference has been stored on the signal. Have a look at the example below:

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

export default component$(() => {
  const store = useStore({
    width: 0,
    height: 0,
  });
  const outputRef = useSignal<Element>();
  useClientEffect$(() => {
    if (outputRef.value) {
      const rect = outputRef.value.getBoundingClientRect();
      store.width = Math.round(rect.width);
      store.height = Math.round(rect.height);
    }
  });

  return (
    <div>
      <div style={{ border: '1px solid red', width: '100px' }} ref={outputRef}>
        Change text value here to stretch the box.
      </div>
      <div>
        The above red box is {store.height} pixels high and {store.width} pixels wide.
      </div>
    </div>
  );
});

Lazy Loading

The component also serves an important role when breaking parent-child relationships for bundling purposes.

export const Child = () => <span>child</span>;

const Parent = () => (
  <section>
    <Child />
  </section>
);

In the above example, referring to the Parent component implies a transitive reference to the Child component. When the bundler is creating a chunk, a reference to Parent necessitates bundling Child as well. (Parent internally refers to Child.) These transitive dependencies are a problem because it means that having a reference to the root component will transitively refer to the remainder of the application—something which Qwik tries to avoid explicitly.

export const Child = component$(() => {
  return <span>child</span>;
});

export const Parent = component$(() => {
  return (
    <section>
      <Child />
    </section>
  );
});

In the above example the Optimizer transforms the above to:

const Child = componentQrl(qrl('./chunk-a', 'Child_onMount'));
const Parent = componentQrl(qrl('./chunk-b', 'Parent_onMount'));
const Parent_onMount = () => qrl('./chunk-c', 'Parent_onRender');
const Parent_onRender = () => (
  <section>
    <Child />
  </section>
);

NOTE For simplicity, not all of the transformations are shown; all resulting symbols are kept in the same file for succinctness.

Notice that after the Optimizer transforms the code, the Parent no longer directly references Child. This is important because it allows the bundler (and tree shakers) to freely move the symbols into different chunks without pulling the rest of the application with it.

So what happens when the Parent component renders and Child component has not yet been downloaded? First, the Parent component renders its JSX like so.

<div>
  <section>
    <div></div>
  </section>
</div>

As you can see in the above example, the <div/> acts as a marker where the Child component will be inserted once it is lazy-loaded.

Mental Model

The Optimizer splits Qwik components into the host element and the behavior of the component. The host element gets bundled with the parent component's OnRender method, whereas the component's behavior is something that gets lazy-loaded on an as-needed basis.

API Overview

State

Styles

Events

  • useOn() - appends a listener to the current component programatically
  • useOnWindow() - appends a listener to the window object programatically
  • useOnDocument() - appends a listener to the document object programatically

Lifecycles

  • useTask$() - defines a callback that will be called before render and/or when a watched store changes
  • useResource$() - creates a resource to asyncronously load data
  • useClientEffect$() - defines a callback that will be called after render in the client only (browser)

Other

  • $() - creates a QRL
  • noSerialize()
  • useErrorBoundary()

Components

  • <Slot> - declares a content projection slot
  • <SSRStreamBlock> - declares a stream block
  • <SSRStream> - declares a stream
  • <Fragment> - declares a JSX fragment

See Also

Made with ❤️ by