diff --git a/packages/docs/docs/getting-started.md b/packages/docs/docs/getting-started.md index f2f5add..17d32cf 100644 --- a/packages/docs/docs/getting-started.md +++ b/packages/docs/docs/getting-started.md @@ -301,33 +301,19 @@ const ResourceView = Component.make("Resource")(function* () { with the context already available in the component body, so the resource above is owned by the component's root scope. -Other lifecycle hooks need a cleanup boundary narrower than the whole -component, so they create a new scope and provide it to their setup Effect: - -```text -React component instance -└── Component root scope - ├── Component body uses the root scope - ├── useOnMount setup uses the root scope - ├── useOnChange setup for dependencies A gets its own scope - ├── useReactEffect setup gets its own scope - └── useReactLayoutEffect setup gets its own scope -``` - -The narrower scopes are managed by their hooks rather than being implicit -Effect child scopes. React unmounting closes all of them; dependency changes -can close and replace a hook scope without closing the component root scope. +`useOnChange`, `useReactEffect`, and `useReactLayoutEffect` each create a scope +that can be replaced without closing the component root scope. The main lifecycle choices are: -| Hook | Scope seen by setup | Scope closes when | -| --- | --- | --- | -| Component body | Component root scope | The component unmounts | -| `useOnMount` | The same component root scope | The component unmounts | -| `useOnChange` | A new dependency scope | Dependencies change or the component unmounts | -| `useReactEffect` | A new effect scope | Dependencies change or the component unmounts | -| `useReactLayoutEffect` | A new layout-effect scope | Dependencies change or the component unmounts | -| `useLayer` | A dependency scope created through `useOnChange` | The layer reference changes or the component unmounts | +| Hook | What it does | Scope seen by setup | Scope closes when | +| --- | --- | --- | --- | +| Component body | Produces the component's rendered value | Component root scope | The component unmounts | +| `useOnMount` | Computes and caches a value for the component instance | The same component root scope | The component unmounts | +| `useOnChange` | Recomputes a cached value when dependencies change | A new dependency scope | Dependencies change or the component unmounts | +| `useReactEffect` | Runs a post-commit side effect | A new effect scope | Dependencies change or the component unmounts | +| `useReactLayoutEffect` | Runs a layout effect before the browser paints | A new layout-effect scope | Dependencies change or the component unmounts | +| `useLayer` | Builds and provides a layer context | A dependency scope created through `useOnChange` | The layer reference changes or the component unmounts | `useRunSync`, `useRunPromise`, `useCallbackSync`, and `useCallbackPromise` do not create lifecycle scopes either. They capture the component context, so an @@ -336,11 +322,9 @@ provides another one. ### useOnMount -`Component.useOnMount` computes a value during the component's initial render -and returns the cached value on later renders. It does not install a scope of -its own: its Effect inherits the root scope provided to the component body. -Acquired resources and forked fibers are therefore tied to the full component -lifetime: +`Component.useOnMount` computes a value during the initial render and caches it +for later renders. It uses the component root scope, so its resources live +until the component unmounts. ```tsx title="src/LocalStateView.tsx" import { Effect, SubscriptionRef } from "effect" @@ -372,10 +356,9 @@ asynchronous and will suspend the component. ### useOnChange -`Component.useOnChange` has the same render-time constraint, but owns a scope -for one dependency window. It creates that scope and provides it to the setup -Effect instead of the component root scope. When a dependency changes, the -previous dependency scope is closed and a new value is computed: +`Component.useOnChange` computes and caches a value, then recomputes it when its +dependencies change. Each dependency set gets its own scope, which closes when +the dependencies change or the component unmounts. ```tsx const label = yield* Component.useOnChange( @@ -396,10 +379,12 @@ read by the setup Effect. ### useReactEffect and useReactLayoutEffect -Use `Component.useReactEffect` for work that should start only after React has -committed the render. The hook creates a fresh scope, provides it to the setup -Effect, and closes it through React effect cleanup. Setup must complete -synchronously, but it can fork asynchronous work into that scope: +`Component.useReactEffect` runs side effects after React commits, while +`Component.useReactLayoutEffect` runs before the browser paints. Each hook owns +a scope that closes when its dependencies change or the component unmounts. + +Setup must complete synchronously, but it can fork asynchronous work into the +hook scope: ```tsx yield* Component.useReactEffect( @@ -408,21 +393,6 @@ yield* Component.useReactEffect( ) ``` -When dependencies change or the component unmounts, the hook closes its scope -and runs registered finalizers. Use `Component.useReactLayoutEffect` for the -same ownership model when setup must happen before the browser paints, such as -DOM measurement. - -The component root scope and scopes created by `useOnChange` debounce finalizer -execution by 100 milliseconds by default. This reduces unnecessary teardown -during rapid unmount/remount cycles, including development behavior. Configure -the root through `Component.withOptions({ finalizerExecutionDebounce: ... })`, -or pass `finalizerExecutionDebounce` in the `useOnChange` options. - -`useReactEffect` and `useReactLayoutEffect` instead close their scopes directly -from React cleanup. Their `finalizerExecutionMode` option controls whether that -close runs synchronously or is forked; it defaults to `"fork"`. - React Strict Mode may intentionally repeat development-only render and effect setup. Initializers and resource acquisition should therefore be safe to run more than once; production retains the normal component lifetime semantics.