Update docs
Lint / lint (push) Failing after 41s

This commit is contained in:
Julien Valverdé
2026-07-22 03:34:27 +02:00
parent 36c47e1b4b
commit 22933b2a09
+78 -16
View File
@@ -271,20 +271,76 @@ Effect View hooks are still React hooks internally. Call them unconditionally
at the top level of the component body, in a consistent order, and never inside at the top level of the component body, in a consistent order, and never inside
branches, loops, event handlers, or nested callbacks. branches, loops, event handlers, or nested callbacks.
### The component root scope
Every rendered Effect View component gets a root `Scope.Scope`. Effect View
creates it when the component instance is rendered, provides it to the entire
component body, and closes it when that React component unmounts.
Effects yielded directly from the body therefore see the root scope. This also
means that `Effect.addFinalizer`, `Effect.acquireRelease`, and
`Effect.forkScoped` can be used naturally inside component setup:
```tsx
import { Effect } from "effect"
import { Component } from "effect-view"
const ResourceView = Component.make("Resource")(function* () {
const resource = yield* Component.useOnMount(() =>
Effect.acquireRelease(
openResource,
(resource) => closeResource(resource),
),
)
return <p>{resource.name}</p>
})
```
`useOnMount` does **not** create or provide another scope. It runs its Effect
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.
The main lifecycle choices are: The main lifecycle choices are:
| Need | Hook | When setup runs | | Hook | Scope seen by setup | Scope closes when |
| --- | --- | --- | | --- | --- | --- |
| Compute and cache a value for this component instance | `useOnMount` | During the initial render | | Component body | Component root scope | The component unmounts |
| Recompute a value when dependencies change | `useOnChange` | During render when dependencies change | | `useOnMount` | The same component root scope | The component unmounts |
| Run a post-commit side effect | `useReactEffect` | After React commits | | `useOnChange` | A new dependency scope | Dependencies change or the component unmounts |
| Measure or update layout before paint | `useReactLayoutEffect` | After DOM mutation, before paint | | `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 |
`useRunSync`, `useRunPromise`, `useCallbackSync`, and `useCallbackPromise` do
not create lifecycle scopes either. They capture the component context, so an
Effect invoked through them sees the component root scope unless it explicitly
provides another one.
### useOnMount ### useOnMount
`Component.useOnMount` computes a value during the component's initial render `Component.useOnMount` computes a value during the component's initial render
and returns the cached value on later renders. It receives the component scope, and returns the cached value on later renders. It does not install a scope of
so acquired resources and forked fibers can be tied to the component lifetime: 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:
```tsx title="src/LocalStateView.tsx" ```tsx title="src/LocalStateView.tsx"
import { Effect, SubscriptionRef } from "effect" import { Effect, SubscriptionRef } from "effect"
@@ -317,8 +373,9 @@ asynchronous and will suspend the component.
### useOnChange ### useOnChange
`Component.useOnChange` has the same render-time constraint, but owns a scope `Component.useOnChange` has the same render-time constraint, but owns a scope
for one dependency window. When a dependency changes, the previous scope is for one dependency window. It creates that scope and provides it to the setup
closed and a new value is computed: Effect instead of the component root scope. When a dependency changes, the
previous dependency scope is closed and a new value is computed:
```tsx ```tsx
const label = yield* Component.useOnChange( const label = yield* Component.useOnChange(
@@ -340,8 +397,9 @@ read by the setup Effect.
### useReactEffect and useReactLayoutEffect ### useReactEffect and useReactLayoutEffect
Use `Component.useReactEffect` for work that should start only after React has Use `Component.useReactEffect` for work that should start only after React has
committed the render. Setup must complete synchronously, but it can fork committed the render. The hook creates a fresh scope, provides it to the setup
asynchronous work into the provided scope: Effect, and closes it through React effect cleanup. Setup must complete
synchronously, but it can fork asynchronous work into that scope:
```tsx ```tsx
yield* Component.useReactEffect( yield* Component.useReactEffect(
@@ -355,11 +413,15 @@ and runs registered finalizers. Use `Component.useReactLayoutEffect` for the
same ownership model when setup must happen before the browser paints, such as same ownership model when setup must happen before the browser paints, such as
DOM measurement. DOM measurement.
Component and dependency scopes debounce finalizer execution by 100 The component root scope and scopes created by `useOnChange` debounce finalizer
milliseconds by default. This reduces unnecessary teardown during rapid execution by 100 milliseconds by default. This reduces unnecessary teardown
unmount/remount cycles, including development behavior. Configure during rapid unmount/remount cycles, including development behavior. Configure
`finalizerExecutionDebounce` through `Component.withOptions` or the relevant the root through `Component.withOptions({ finalizerExecutionDebounce: ... })`,
hook options when cleanup timing must differ. 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 React Strict Mode may intentionally repeat development-only render and effect
setup. Initializers and resource acquisition should therefore be safe to run setup. Initializers and resource acquisition should therefore be safe to run