[]previous and next page

Ownership and memory

Everything a component creates (effects, memos, cleanups) belongs to the owner it was created under and is disposed with it.

For agentsUpdated

Everything a component creates (effects, memos, cleanups) belongs to the owner it was created under and is disposed with it. A leak is something created outside an owner or that outlives it:

  • Effects at module scope have no owner and are never disposed. Core warns NO_OWNER_EFFECT in development. Wrap them in createRoot and keep the dispose function.
  • Timers, listeners, observers, sockets: register their teardown with onCleanup (or return it from useEffect) in the same scope that created them.
  • Callbacks after await lose the owner. Capture it with getOwner() and use runWithOwner.
  • Detached roots (createRoot(fn, { detached: true })) are disposed only by their own dispose.
src/lifecycle.tsxtsx
import { onCleanup, useEffect } from "@shadow-js/core";
import { Show } from "@shadow-js/web";

export const live = { listeners: 0, created: 0, disposed: 0 };

export function Tracker() {
  live.created += 1;
  const onResize = (): void => undefined;
  window.addEventListener("resize", onResize);
  live.listeners += 1;
  onCleanup(() => {
    window.removeEventListener("resize", onResize);
    live.listeners -= 1;
    live.disposed += 1;
  });
  useEffect(() => {
    // Tracked work would go here; its own cleanup runs before each rerun.
  });
  return <i>tracking</i>;
}

export function Panel(props: { open: () => boolean }) {
  return (
    <Show when={props.open()}>
      <Tracker />
    </Show>
  );
}