Add Effect v4 support, Fast Refresh tooling, and revamped docs #56
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user