Components
Components are the basic building blocks of Qwik Applications. They are reusable piece of code that can be used to build a UI.
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 Qwik component is a function that returns JSX wrapped in a component$
call.
import { component$ } from '@builder.io/qwik';
export default component$(() => {
return <div>Hello World!</div>;
});
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 (
<>
<p>Parent Text</p>
<Child />
</>
);
});
const Child = component$(() => {
return <p>Child Text</p>;
});
Notice that Qwik components are already lazy loaded thanks to the
$
sign. That means that you don't need to dynamically import the child component manually, Qwik will do it for you.
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 (
<>
<p>Count: {count.value}</p>
<button onClick$={() => count.value++}>Increment</button>
</>
);
});
Props
Props are used to pass data from the parent into the component. Props are accesible via the props
argument to the component$ function.
In this example a component Item
declares optional name
, quantity
, description
, and price
.
import { component$ } from '@builder.io/qwik';
interface ItemProps {
name?: string;
quantity?: number;
description?: string;
price?: number;
}
export const Item = component$<ItemProps>((props) => {
return (
<ul>
<li>name: {props.name}</li>
<li>quantity: {props.quantity}</li>
<li>description: {props.description}</li>
<li>price: {props.price}</li>
</ul>
);
});
export default component$(() => {
return (
<>
<h1>Props</h1>
<Item name="hammer" price={9.99} />
</>
);
});
In the example above we are using
component$<ItemProps>
to provide an explicit type for the props. This is optional but it allows the TypeScript compiler to check that the props are used correctly.
Default props
You can use the destructuring pattern with props to provide default values.
interface Props {
enabled?: boolean;
placeholder?: string;
}
// We can use JS's destructuring of props to provide a default value.
export default component$<Props>(({enabled = true, placeholder = ''}) => {
return (
<input disabled={!enabled} placeholder={placeholder} />
);
});
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.
Getting a reference to DOM elements can be useful to calculate the element size (
getBoundingClientRect
), computed styles, initializing a WebGL canvas, or even wire-up some third-party library that interacts with DOM elements directly.
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 (
<section>
<article
ref={outputRef}
style={{ border: '1px solid red', width: '100px' }}
>
Change text value here to stretch the box.
</article>
<p>
The above red box is {height.value} pixels high and {width.value}{' '}
pixels wide.
</p>
</section>
);
});
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 <p>child</p>;
});
export const Parent = component$(() => {
return (
<section>
<Child />
</section>
);
});
export default Parent;
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.
<main>
<section>
<!--qv--><!--/qv-->
</section>
</main>
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.
Inline Components
In addition to the standard component$()
with all of it's lazy-loaded
properties, Qwik also supports lightweight (inline) components that act more
like components in traditional frameworks.
import { component$ } from '@builder.io/qwik';
// Inline component: declared using a standard function.
export const MyButton = (props: { text: string }) => {
return <button>{props.text}</button>;
};
// Component: declared using `component$()`.
export default component$(() => {
return (
<p>
Some text:
<MyButton text="Click me" />
</p>
);
});
In the above example, MyButton
is an inline component.
Unlike the standard component$()
, inline components cannot be individually
lazy-loaded; instead, they are bundled with their parent component. In this case:
MyButton
will get bundled with thedefault
component.- Whenever
default
is rendered, it will also guarantee thatMyButton
is rendered.
You can think of inline components as being inlined into the component where they are instantiated.
Limitations
Inline components come with some limitations that the standard component$()
does not have. Inline components:
- Cannot use
use*
methods such asuseSignal
oruseStore
. - Cannot project content with a
<Slot>
.
As the name implies, inline components are best used sparingly for lightweight pieces of markup since they offer the convenience of being bundled with the parent component.
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 programmaticallyuseOnWindow()
- appends a listener to the window object programmaticallyuseOnDocument()
- appends a listener to the document object programmatically
Tasks/Lifecycle
useTask$()
- defines a callback that will be called before render and/or when a watched store changesuseVisibleTask$()
- defines a callback that will be called after rendering in the client only (browser)useResource$()
- creates a resource to asynchronously load data
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