Lifecycles
Thanks to Resumability, components life and its lifecycle extend across server and browser. Sometimes the component will be first rendered in the server, sometimes it could be in the browser, however, in both cases the lifecycle will be the same, only its execution happens in different environments.
Usually the life of a component starts on the server (during SSR or SSG), in that case, the hooks will run like this:
useTask$ --> RENDER --> useClientEffect$
|
| ------ SERVER ------ | ---- BROWSER ---- |
|
pause|resume
Notice that because the component was mounted in the server, only useClientEffect$() runs in the browser. This is because the browser continues the same lifecycle, that was paused in the server right after the render and resumed in the browser.
Sometimes a component will be first mounted/rendered in the browser, for example when the user SPA navigates to a new page, or a "modal" component first appears in the page. In that case, the lifecycle will run like this:
useTask$ --> RENDER --> useClientEffect$
| --------------- BROWSER --------------- |
Notice that the lifecycle is exactly the same, but this time all the hooks run in the browser, and non in the server.
useTask$()
- When: BEFORE component's first render, and when tracked state changes
- Times: at least once
- Platform: server and browser
useTask$()
registers a hook to be executed upon component creation, it will run at least once either in the server or in the browser, depending where the component is initially rendered.
Additionally, this task can be reactive and re-execute when some tracked signal or store changes, like this:
useTask$(({ track }) => {
track(() => store.count);
// will run when the component mounts and every time "store.count" changes
});
Notice that any subsequent execution will always happen in the browser, because reactivity is a client-only thing.
useTask$(track state) -> RENDER -> CLICK (state changes) -> RE-RUN useTask$(track state)
โโโโโโโโโโโโโโโ | โโโโโโโโโโโโโโโ
| --------- SERVER --------- | --------------- BROWSER --------------- |
|
pause|resume
If
useTask$()
does not track any state, it will run exactly once, either in the server or in the browser (not both), depending where the component is initially rendered. Effectively behaving like an "on mount" hook.
useTask$()
will block the rendering of the component until after its async callback resolves, in another words, tasks execute sequentially even if they are asynchronous. (Only one task executes at a time / Tasks block rendering).
Example
The useTask$()
function is used to observe the state.count
property. Any changes to the state.count
cause the arrow function to execute which in turn updates the state.doubleCount
to the double of state.count
.
import { component$, useStore, useTask$ } from '@builder.io/qwik';
export const Cmp = component$(() => {
const store = useStore({
count: 1,
doubleCount: 0,
});
// this task will be called exactly once, either on the server or on the browser
useTask$(() => {
console.log('component mounted');
});
// this task will be called at mount and every time `store.count` changes
useTask$(({ track }) => {
const count = track(() => store.count);
store.doubleCount = 2 * count;
});
return (
<div>
{store.count} / {store.doubleCount}
</div>
);
});
The example above uses the track
function to watch changes in () => store.count
. The callback will run once in the SSR when the component is mounted and every time store.count
changes.
Notice that
useTask$()
runs BEFORE the actual rendering and in the server, so if you need to do any DOM manipulation, useuseClientEffect$()
instead, which runs on the client after rendering.
useTask$()
when you need to
Use - Run async tasks before rendering
- Run code only once before component is first rendered
- Compute derived state from tracked state (without causing potential re-rendering like in
useClientEffect$
) - Programmatically run code when state changes
Note, if you're thinking about load data (like using
fetch()
) inside ofuseTask$
, consider usinguseResource$()
instead. This API is more efficient in terms of leveraging SSR streaming and parallel data fetching.
Server/Browser only task
Sometimes it is required to only run code either in the server or in the client. This can be achieved by using the isServer
and isBrowser
booleans exported from @builder.io/qwik/build
.
import { isServer, isBrowser } from '@builder.io/qwik/build';
export const Cmp = component$(() => {
const store = useStore({
users: [],
});
useTask$(async () => {
if (isServer) {
// If the component is mounted on the server, call DB directly.
store.users = await db.requestUsers();
}
if (isBrowser) {
// If the component is mounted on the browser, fetch users through API.
store.users = await fetchGetUsers();
}
});
return (
<>
{store.users.map((user) => (
<User user={user} />
))}
</>
);
});
But as mentioned above - for data fetching, consider using
useResource$()
instead.
useClientEffect$()
- When: AFTER component's first render and on tracked state changes
- Times: at least once
- Platform: browser only
For all components that got mounted during SSR (in the server), the useClientEffect$()
will run eagarly, that means, without user interaction, but the eagerness can be configured!
useClientEffect$(() => console.log('runs in the browser'), {
eagerness: 'visible', // 'load' | 'visible' | 'idle'
});
This is a unique feature of Qwik, any other frameworks would execute this and other code as part of hydration, but in Qwik, you can actually specify when it happens:
"visible"
: (this is the default) when the component becomes visible in the viewport (uses Intersection Observer under the hood)."load"
: when the documents finish loading (the document's "load" event)"idle"
: after load the first moment the site becomes idle. It usesrequestIdleCallback()
under the hood.
Example
export const Timer = component$(() => {
const store = useStore({
count: 0,
});
useClientEffect$(() => {
// Only runs in the client
const timer = setInterval(() => {
store.count++;
}, 500);
return () => {
clearInterval(timer);
};
});
return <>{store.count}</>;
});
NOTE: Don't abuse
useClientEffect$()
when the same logic can be achieved usinguseTask$()
or other means. Ask to yourself: Does this code really need to run at the beginning in the browser? If the answer is no,useClientEffect$()
is probably not the right answer.
useClientEffect$()
When to use Use this hook when you need to run JS right at loading time of the page, even if the user never interacts with the page.
For example:
- Read the DOM after rendering
- Initialize some animations
- WebGL logic
- Use DOM APIs like
localStorage
- Run some code without user interaction, like a setInterval()
useEffect()
?
How does it compare with React's Both APIs share a lot of semantics, but while both run AFTER rendering, useClientEffect$()
can run also independently from rendering.
Use Method Rules
When using lifecycle hooks, you must adhere to the following rules:
- They can only be called at the root level of
component$
(not inside conditional blocks) - They can only be called at the root of another
use*
method, allowing for composition.
useHook(); // <-- โ does not work
export default component$(() => {
useCustomHook(); // <-- โ
does work
if (condition) {
useHook(); // <-- โ does not work
}
useTask$(() => {
useNavigate(); // <-- โ does not work
});
const myQrl = $(() => useHook()); // <-- โ does not work
return <button onClick$={() => useHook()}></button>; // <-- โ does not work
});
function useCustomHook() {
useHook(); // <-- โ
does work
if (condition) {
useHook(); // <-- โ does not work
}
}