Lifecycles
Rules
When using lifecycle hooks, you must adhere to the following rules:
- They can only be called in
component$
- They can only be called at the root level of a function / arrow function context, not inside of branches or conditional blocks
- They can only be called from another
use*$
method, allowing for composition
useHook() // <-- ❌ does not work
export default component$(() => {
useHook() // <-- ✅ does work
if (condition) {
useHook() // <-- ❌ does not work
}
const myQrl = $(() => useHook()) // <-- ✅ does work
return <button onClick$={() => useHook() }></button> // <-- ✅ does work
})
const useCustomHook = () => {
useHook() // <-- ✅ does work
}
useMount$()
useMount$()
registers a hook to be executed upon component creation. useMount$()
will block the rendering of the component until after the useMount$()
callback resolves. (This is useful for fetching asynchronous data and delaying rendering until data is received, ensuring that the rendered component contains the data)
While useMount$()
can execute on either the server or on the client, it runs exactly once. (Either on the server or on the client, depending on where the component got first rendered)
See also the useServerMount$()
hook which has the same semantics but only runs on the server.
Example
const Cmp = component$(() => {
const store = useStore({
users: [],
});
useMount$(async () => {
// This code will run on component creation to fetch the data.
store.users = await db.requestUsers();
});
return (
<>
{store.users.map((user) => (
<User user={user} />
))}
</>
);
});
interface User {
name: string;
}
function User(props: { user: User }) {
return <div>Name: {props.user.name}</div>;
}
useServerMount$()
useServerMount$()
registers a server-mounted hook that only runs on the server when the component is first mounted.
Example
const Cmp = component$(() => {
const store = useStore({
users: [],
});
useServerMount$(async () => {
// This code will ONLY run once in the server, when the component is mounted
store.users = await db.requestUsers();
});
return (
<>
{store.users.map((user) => (
<User user={user} />
))}
</>
);
});
interface User {
name: string;
}
function User(props: { user: User }) {
return <div>Name: {props.user.name}</div>;
}
useWatch$()
Re-runs the watchFn
when the tracked inputs change.
Use useWatch
to track changes on a set of inputs, and then re-execute the watchFn
when those inputs change.
The watchFn
only executes if the tracked inputs change. To track the inputs, use the track
function to wrap property reads. This will create subscriptions that will trigger the watchFn
to re-run.
See also the useClientEffect$()
hook that has the same semantics but only runs on the client.
Example
The useWatch
function is used to observe the state.count
property. Any changes to the state.count
cause the watchFn
to execute which in turn updates the state.doubleCount
to the double of state.count
.
const Cmp = component$(() => {
const store = useStore({
count: 0,
doubleCount: 0,
debounced: 0,
});
// Double count watch
useWatch$(({ track }) => {
const count = track(store, 'count');
store.doubleCount = 2 * count;
});
// Debouncer watch
useWatch$(({ track }) => {
const doubleCount = track(store, 'doubleCount');
const timer = setTimeout(() => {
store.debounced = doubleCount;
}, 2000);
return () => {
clearTimeout(timer);
};
});
return (
<>
<div>
{store.count} / {store.doubleCount}
</div>
<div>{store.debounced}</div>
</>
);
});
useClientEffect$()
Re-runs the watchFn
when the tracked inputs change.
If no track
is being used, it will execute exactly once.
Example
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}</>;
});