@@ -52,6 +52,14 @@ valid decoded value should go:
|
||||
| `MutationForm` | Yes | It is passed to a mutation when `submit` runs. |
|
||||
| `LensForm` | Yes | It is written automatically to a target `Lens`. |
|
||||
|
||||
## Create forms once
|
||||
|
||||
`MutationForm.service` and `LensForm.service` are Effect constructors, not
|
||||
hooks. Create each root form once and keep it stable. For a component-owned
|
||||
form, call the constructor inside `Component.useOnMount`; for a form shared by
|
||||
multiple components, create it in an Effect service. Update the existing form's
|
||||
Lenses rather than reconstructing the form during render.
|
||||
|
||||
## MutationForm: validate, then submit
|
||||
|
||||
Use `MutationForm` for registration, checkout, search, and other workflows with
|
||||
@@ -102,9 +110,9 @@ mutation receives the schema's decoded profile, so `profile.age` is a number.
|
||||
Schema transformations happen before the mutation and schema issues prevent an
|
||||
invalid draft from being submitted.
|
||||
|
||||
`MutationForm.service` also starts initial validation in the current scope. Use
|
||||
it inside `Component.useOnMount`, a scoped service, or another Effect scope so
|
||||
its validation and mutation work is cleaned up with its owner.
|
||||
`MutationForm.service` also starts initial validation in the current scope. In
|
||||
the example above, `Component.useOnMount` keeps that validation and the form's
|
||||
mutation work tied to the component that owns them.
|
||||
|
||||
## LensForm: validate, then synchronize
|
||||
|
||||
|
||||
@@ -295,24 +295,25 @@ 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.
|
||||
|
||||
`useOnChange`, `useReactEffect`, and `useReactLayoutEffect` each create a scope
|
||||
that can be replaced without closing the component root scope.
|
||||
`useOnChange`, `useReactEffect`, and `useReactLayoutEffect` each manage their
|
||||
own resources. When their dependencies change, those resources are cleaned up
|
||||
and recreated while the rest of the component remains active.
|
||||
|
||||
The main lifecycle choices are:
|
||||
|
||||
| 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 |
|
||||
| `useOnMount` | Computes and caches a value for the component instance | 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 |
|
||||
| `useLayer` | Builds and provides a layer context | A new scope tied to the current layer reference | 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.
|
||||
`useRunSync` and `useRunPromise` do not create a new scope. The runner they
|
||||
return includes the component root scope in its context by default.
|
||||
`useCallbackSync` and `useCallbackPromise` similarly capture the component
|
||||
context required by their Effect.
|
||||
|
||||
### useOnMount
|
||||
|
||||
@@ -410,6 +411,21 @@ Use `Component.useRunSync` only for Effects known to complete synchronously.
|
||||
Use `Component.useRunPromise` for Effects that may suspend, sleep, fetch, or
|
||||
otherwise continue asynchronously.
|
||||
|
||||
Both runners provide `Scope.Scope` by default, so they can run scoped Effects
|
||||
without a type argument. When an Effect also requires application services,
|
||||
declare the complete runner context explicitly:
|
||||
|
||||
```tsx
|
||||
import { Scope } from "effect"
|
||||
|
||||
const runPromise = yield* Component.useRunPromise<
|
||||
Scope.Scope | UserRepository
|
||||
>()
|
||||
```
|
||||
|
||||
The resulting runner accepts Effects that require the component scope,
|
||||
`UserRepository`, or both.
|
||||
|
||||
When a callback is passed to a memoized child or used as a dependency, use the
|
||||
callback variants to preserve its identity:
|
||||
|
||||
|
||||
@@ -30,6 +30,11 @@ Create a mutation in a component scope with `Mutation.make`. Its function can
|
||||
be any Effect, including one that requires services from the application
|
||||
runtime:
|
||||
|
||||
`Mutation.make` is an Effect constructor, not a hook. Create each mutation
|
||||
instance once and keep it stable instead of calling `Mutation.make` again on
|
||||
every render. Use `Component.useOnMount` for a component-owned mutation or
|
||||
create it in an Effect service when it should be shared.
|
||||
|
||||
```tsx
|
||||
import { Effect } from "effect"
|
||||
import { AsyncResult } from "effect/unstable/reactivity"
|
||||
|
||||
@@ -35,7 +35,6 @@ application runtime once, alongside the services used by your query effects:
|
||||
|
||||
```tsx title="src/runtime.ts"
|
||||
import { Layer } from "effect"
|
||||
import { FetchHttpClient } from "effect/unstable/http"
|
||||
import { QueryClient, ReactRuntime } from "effect-view"
|
||||
|
||||
const AppLive = Layer.empty.pipe(
|
||||
@@ -44,7 +43,6 @@ const AppLive = Layer.empty.pipe(
|
||||
defaultRefreshOnWindowFocus: true,
|
||||
cacheGcTime: "5 minutes",
|
||||
})),
|
||||
Layer.provideMerge(FetchHttpClient.layer),
|
||||
)
|
||||
|
||||
export const runtime = ReactRuntime.make(AppLive)
|
||||
@@ -60,6 +58,12 @@ A query is driven by a `View` rather than by a value read during one React
|
||||
render. Whenever that key changes, `Query.service` checks the cache and starts
|
||||
the query effect when necessary.
|
||||
|
||||
`Query.service` is an Effect constructor, not a hook. Create each query instance
|
||||
once and keep it stable. In a component, the usual place is
|
||||
`Component.useOnMount`; a query shared by multiple components can instead be
|
||||
owned by an Effect service. Change the existing query's reactive key rather
|
||||
than reconstructing the query during render.
|
||||
|
||||
```tsx
|
||||
import { Effect, Schema, SubscriptionRef } from "effect"
|
||||
import { HttpClient } from "effect/unstable/http"
|
||||
@@ -118,9 +122,9 @@ Effect equality by default, so structurally equal Effect data types work well
|
||||
as query keys. Supply `keyEquivalence` when the key needs different equality
|
||||
semantics.
|
||||
|
||||
`Query.service` creates the query and starts watching its key in the current
|
||||
scope. Creating it in `Component.useOnMount` keeps one query instance—and one
|
||||
stable query function identity—for the component's lifetime.
|
||||
`Query.service` starts watching its key in the current scope. The
|
||||
`Component.useOnMount` call above keeps both the query instance and its query
|
||||
function identity stable for the component's lifetime.
|
||||
|
||||
## Render AsyncResult
|
||||
|
||||
|
||||
Reference in New Issue
Block a user