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}</>;
});
Made with ❤️ by