Components
Components are the 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.
- They are resumable (a component can get created on a server and continue its execution on the client).
- They are reactive and render independently of other components on the page. See rendering.
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$ } from '@builder.io/qwik';
export default component$(() => {
return <span>Hello World!</span>;
});
The reason for the
component$is that the trailing$allows the Optimizer to break the components into an application tree into a separate chunk so that each chunk can be loaded (or not loaded if it is not needed) independently. Without the$the component would be always loaded if the parent component needs to be loaded.
Composing Components
Components can be composed together to create more complex components.
import { component$ } from '@builder.io/qwik';
export default component$(() => {
return (
<>
<div>Parent Text</div>
<Child />
</>
);
});
const Child = component$(() => {
return <div>Child Text</div>;
});
Counter Example
A slightly more complex example of a counter.
import { component$, useSignal } from '@builder.io/qwik';
export default component$(() => {
const count = useSignal(0);
return (
<>
<div>Count: {count.value}</div>
<button onClick$={() => count.value++}>Increment</button>
</>
);
});
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.
import { component$ } from '@builder.io/qwik';
export default component$(() => {
return (
<>
<h1>Props</h1>
<Item name="hammer" price={9.99} />
</>
);
});
interface ItemProps {
name?: string;
quantity?: number;
description?: string;
price?: number;
}
export const Item = component$<ItemProps>(({ name, quantity, description, price }) => {
return (
<ul>
<li>name: {name}</li>
<li>quantity: {quantity}</li>
<li>description: {description}</li>
<li>price: {price}</li>
</ul>
);
});
Rendering on Reactivity
Qwik components are reactive. This means that they automatically update on a state change. There are two kinds of updates:
- A state is bound to a DOM text or attribute. Such changes usually directly update the DOM and do not require component function re-execute.
- A state causes a structural change to the DOM (elements are created and or removed). Such changes require component function to re-execute.
The thing to keep in mind is that when state changes your component function may execute zero or more times depending on what the state is bound to. For this reason, the function should be idempotent and you should not rely on the number of times it executes.
A state change causes the component to get 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.
Getting hold of DOM element
Use ref to get hold of a DOM element. Create a signal to store DOM element. Then pass the signal to the JSX ref property.
import { component$, useVisibleTask$, useSignal } from '@builder.io/qwik';
export default component$(() => {
const width = useSignal(0);
const height = useSignal(0);
const outputRef = useSignal<Element>();
useVisibleTask$(() => {
if (outputRef.value) {
const rect = outputRef.value.getBoundingClientRect();
width.value = Math.round(rect.width);
height.value = Math.round(rect.height);
}
});
return (
<div>
<div ref={outputRef} style={{ border: '1px solid red', width: '100px' }}>
Change text value here to stretch the box.
</div>
<div>
The above red box is {height.value} pixels high and {width.value} 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.
To avoid the above problem we don't refer to components directly, instead, we refer to the lazy wrapper. This is created automatically by the component$() function.
import { component$ } from '@builder.io/qwik';
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 needs to render a Child component, but the Child component has not yet been downloaded? First, the Parent component renders its DOM like so.
<div>
<section>
<!--qv--><!--/qv-->
</section>
</div>
As you can see in the above example, the <!--qv--> acts as a marker where the Child component will be inserted once it is lazy-loaded.
API Overview
State
useSignal(initialState)- creates a reactive valueuseStore(initialStateObject)- creates a reactive object that can be used to store statecreateContextId(contextName)- creates a context referenceuseContextProvider()- provides a value to a given contextuseContext()- reads the value of the current context
Styles
useStylesScoped$()- appends scoped styles to the componentuseStyles$()- appends unscoped styles to the component
Events
useOn()- appends a listener to the current component programaticallyuseOnWindow()- appends a listener to the window object programaticallyuseOnDocument()- 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 changesuseResource$()- creates a resource to asyncronously load datauseVisibleTask$()- defines a callback that will be called after render in the client only (browser)
Other
$()- creates a QRLnoSerialize()useErrorBoundary()
Components
<Slot>- declares a content projection slot<SSRStreamBlock>- declares a stream block<SSRStream>- declares a stream<Fragment>- declares a JSX fragment