Component anatomy

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';

// Qwik components can be asynchronous
export const MyCmp = component$(async (props: MyCmpProps) => {
  // Declare local state
  const state = useStore({
    count: 0,
  });

  // Returns JSX
  return (
    <Host>
      <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>
    </Host>
  );
});

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

The resulting component can be used like so:

const MyApp = component$(() => {
  return (
    <>
      - With no props: <Item />
      - With some props: <Item description="Item description" />
      - With all props: <Item name="Hammer" quantity={3} description="Best organic hammer" price={10.0} />
    </>
  );
});

Host element

The host element is an element in the DOM that represents component boundaries.

Qwik-components rely on host elements because it must be possible by looking on the HTML to determine where one component starts and another ends. Without knowing the boundaries, it would not be possible to render components independently without forcing parent/child components to render as well. This is a crucial feature of Qwik.

const Child = component$(() => <span>child</span>);

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

Will result in:

<div>
  <section>
    <div>
      <span>child</span>
    </div>
  </section>
</div>

As you can see, component$ will always create an extra element, by default is div, but that can be changed with component$ options argument or the host:tagName attribute:

const MyArticle = component$(() => (
  <span>My article</span>
), {
  tagName: 'article'
});

and:

const MyArticle = component$(() => (
  <span>My article</span>
));

<MyArticle host:tagName="article"/>

Will both result in:

<article>
  <span>
    My article
  </span>
</article>

useHostElement()

Since the host element is implicitly created by component$, it is not possible to access it directly. Instead, you can use useHostElement() to get the host element.

Qwik uses host elements in various ways. For example when using useHostElement() function which retrieves it. It is also used to attach props to the components for serialization.

Host Element Attributes & Styling

Assume you have a component defined as so:

const Greeter = component$((props: {salutation?: string, name: string, }) => {
  ...
});

The component can be used like so:

<Greeter name="World" />

or

<Greeter salutation="Hello" name="World" />

In both above cases the rendered HTML would be:

<div></div>

Because the host element is an actual element, there may be a desire to place HTML classes, styles, or attributes on the host element.

What if you wanted to add a name attribute to the host element? The issue is that the name is already used by the component props. For this reason, we use host: prefix to refer to the host element's attributes.

<Greeter
  host:name="abc"
  host:id="greeter"
  host:onClick$={() => {}}

  name="world"
/>

would render as:

<div name="abc" id="greeter" onClick$="..."></div>

Using a host: prefix allows the developer to control the component's host element attributes independently from the component's props.

One can use the same approach for class and styles.

<Greeter host:class="greeter" host:style={{ backgroundColor: 'red' }} name="world" />

would render:

<div class="greeter" style="background-color: red;"></div>

However, for convenience class and styles are special, and they will automatically map to host:class and host:styles when placed on the host element.

<Greeter
  class="greeter"
  style={{ backgroundColor: 'red' }}
  name="world"
/>

would also render to the same output.

<div class="greeter" style="background-color: red;"></div>

<Host/>

It may also be desirable for a component to set class, style and attributes on the host element associated with the component itself. To do that, one can use <Host> tag.

const Greeter = component$(() => {
  return (
    <Host class="padded" id="greeter">
      <span>Hello World</span>
    </Host>
  );
});

will result in:

<div class="padded" id="greeter">
  <span>Hello World</span>
</div>

Lazy Loading

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

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.

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

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 components OnRender function, whereas the component's behavior is something that gets lazy-loaded on an as-needed basis.

Made with ♡ by the Builder.io team