Refactor API
Lint / lint (push) Successful in 48s

This commit is contained in:
Julien Valverdé
2026-07-30 15:12:53 +02:00
parent e412943e64
commit 66b7a08ef7
10 changed files with 87 additions and 97 deletions
+14 -16
View File
@@ -54,11 +54,9 @@ valid decoded value should go:
## 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.make` and `LensForm.make` construct forms. Pipe them through the
module's `run` operator to start validation or synchronization in the current
scope. Create each root form once and keep it stable.
## MutationForm: validate, then submit
@@ -73,7 +71,7 @@ import { Component, MutationForm, View } from "effect-view"
const CreateProfileView = Component.make("CreateProfile")(function* () {
const form = yield* Component.useOnMount(() =>
MutationForm.service({
MutationForm.make({
schema: ProfileSchema,
initialEncodedValue: {
displayName: "",
@@ -84,7 +82,7 @@ const CreateProfileView = Component.make("CreateProfile")(function* () {
Effect.log(
`Creating ${profile.displayName}, age ${profile.age}`,
),
}),
}).pipe(MutationForm.run),
)
const [canCommit, isCommitting] = yield* View.useAll([
@@ -110,9 +108,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. In
the example above, `Component.useOnMount` keeps that validation and the form's
mutation work tied to the component that owns them.
`MutationForm.run` starts initial validation. 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
@@ -136,10 +134,10 @@ const EditProfileView = Component.make("EditProfile")(function* () {
}),
)
const form = yield* LensForm.service({
const form = yield* LensForm.make({
schema: ProfileSchema,
target: profile,
})
}).pipe(LensForm.run)
return [form, profile] as const
}),
@@ -168,8 +166,8 @@ reaches the target. If another part of the application updates the target,
`LensForm` encodes that value back into the draft.
Pass `initialEncodedValue` only when the first draft should differ from the
encoded target. Otherwise `LensForm.service` obtains the initial draft by
encoding the target through the schema.
encoded target. Otherwise `LensForm.make` obtains the initial draft by encoding
the target through the schema.
## Focus into subforms
@@ -427,14 +425,14 @@ const AppointmentSchema = Schema.Struct({
const AppointmentView = Component.make("Appointment")(function* () {
const [form, startsAtField] = yield* Component.useOnMount(() =>
Effect.gen(function* () {
const form = yield* MutationForm.service({
const form = yield* MutationForm.make({
schema: AppointmentSchema,
initialEncodedValue: { startsAt: "" },
f: ([appointment]) =>
Effect.log(
`Saving ${DateTime.formatIso(appointment.startsAt)}`,
),
})
}).pipe(MutationForm.run)
return [form, Form.focusObjectOn(form, "startsAt")] as const
}),
+12 -10
View File
@@ -57,11 +57,11 @@ client defaults. Window-focus refresh also requires the optional
## Create a reactive query
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
render. Whenever that key changes, a running Query 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
`Query.make` constructs the Query and `Query.run` starts it in the current
scope. 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.
@@ -84,7 +84,7 @@ const PostView = Component.make("Post")(function* () {
yield* SubscriptionRef.make(["post", 1 as number] as const),
)
const query = yield* Query.service({
const query = yield* Query.make({
key,
staleTime: "1 minute",
f: ([, id]) =>
@@ -95,7 +95,7 @@ const PostView = Component.make("Post")(function* () {
Effect.andThen((response) => response.json),
Effect.andThen(Schema.decodeUnknownEffect(Post)),
),
})
}).pipe(Query.run)
return [Lens.focusTupleAt(key, 1), query] as const
}),
@@ -124,7 +124,7 @@ 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` starts watching its key in the current scope. The
`Query.run` 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.
@@ -255,7 +255,8 @@ Refresh every five minutes, starting after five minutes:
```ts
import { Schedule } from "effect"
yield* query.pipe(
const query = yield* Query.make(options).pipe(
Query.run,
Query.withScheduledRefresh(Schedule.spaced("5 minutes")),
)
```
@@ -263,7 +264,8 @@ yield* query.pipe(
Limit the number of refreshes:
```ts
yield* query.pipe(
const query = yield* Query.make(options).pipe(
Query.run,
Query.withScheduledRefresh(
Schedule.spaced("5 minutes").pipe(
Schedule.upTo({ times: 3 }),
@@ -289,12 +291,12 @@ By default, the client enables refresh on browser window focus. Set it globally
or override it for one query:
```tsx
const query = yield* Query.service({
const query = yield* Query.make({
key,
f: loadPost,
staleTime: "10 seconds",
refreshOnWindowFocus: false,
})
}).pipe(Query.run)
```
Window-focus refresh depends on the optional `@effect/platform-browser`