Update docs
Lint / lint (push) Successful in 47s

This commit is contained in:
Julien Valverdé
2026-07-27 03:16:44 +02:00
parent 74cacc8d61
commit b302b31fa9
2 changed files with 151 additions and 118 deletions
+128 -118
View File
@@ -171,124 +171,6 @@ Pass `initialEncodedValue` only when the first draft should differ from the
encoded target. Otherwise `LensForm.service` obtains the initial draft by encoded target. Otherwise `LensForm.service` obtains the initial draft by
encoding the target through the schema. encoding the target through the schema.
## Example: local date input, UTC domain value
The encoded/decoded distinction is especially useful for dates. An HTML
`datetime-local` input produces a wall-clock string such as
`"2026-07-22T14:30"`. It contains no time-zone or UTC offset, while application
state and APIs usually need an unambiguous UTC instant.
A schema can own that entire conversion. The following schema interprets the
input in the current time zone and decodes it to `DateTime.Utc`:
```tsx title="src/DateTimeUtcFromZonedInput.ts"
import { DateTime, Effect, Option, ParseResult, Schema } from "effect"
class DateTimeUtcFromZoned extends Schema.transformOrFail(
Schema.DateTimeZonedFromSelf,
Schema.DateTimeUtcFromSelf,
{
strict: true,
decode: (input) => ParseResult.succeed(DateTime.toUtc(input)),
encode: DateTime.setZoneCurrent,
},
) {}
export class DateTimeUtcFromZonedInput extends Schema.transformOrFail(
Schema.String,
DateTimeUtcFromZoned,
{
strict: true,
decode: (input, _options, ast) =>
Effect.flatMap(DateTime.CurrentTimeZone, (timeZone) =>
Option.match(
DateTime.makeZoned(input, {
timeZone,
adjustForTimeZone: true,
}),
{
onSome: ParseResult.succeed,
onNone: () =>
ParseResult.fail(
new ParseResult.Type(
ast,
input,
"Enter a valid date and time",
),
),
},
),
),
encode: (value) =>
ParseResult.succeed(
DateTime.formatIsoZoned(value).slice(0, 16),
),
},
) {}
```
Use it like any other field schema. The form and input work with a string, but
the mutation receives UTC:
```tsx
import { DateTime, Effect, Schema } from "effect"
import { Component, Form, MutationForm, View } from "effect-view"
import { DateTimeUtcFromZonedInput } from "./DateTimeUtcFromZonedInput"
const AppointmentSchema = Schema.Struct({
startsAt: DateTimeUtcFromZonedInput,
})
const AppointmentView = Component.make("Appointment")(function* () {
const [form, startsAtField] = yield* Component.useOnMount(() =>
Effect.gen(function* () {
const form = yield* MutationForm.service({
schema: AppointmentSchema,
initialEncodedValue: { startsAt: "" },
f: ([appointment]) =>
Effect.log(
`Saving ${DateTime.formatIso(appointment.startsAt)}`,
),
})
return [form, Form.focusObjectOn(form, "startsAt")] as const
}),
)
const startsAt = yield* Form.useInput(startsAtField)
const [canCommit] = yield* View.useAll([form.canCommit])
const runPromise = yield* Component.useRunPromise()
return (
<form
onSubmit={(event) => {
event.preventDefault()
void runPromise(form.submit)
}}
>
<input
type="datetime-local"
value={startsAt.value}
onChange={(event) =>
startsAt.setValue(event.currentTarget.value)
}
/>
<button disabled={!canCommit}>Save appointment</button>
</form>
)
})
```
The schema requires `DateTime.CurrentTimeZone`; provide
`DateTime.layerCurrentZoneLocal` in the application runtime. If the current
zone is `Europe/Paris`, for example, entering `2026-07-22T14:30` decodes to the
UTC instant `2026-07-22T12:30:00.000Z`.
Encoding works in the other direction. A `LensForm` whose target contains that
UTC instant presents `2026-07-22T14:30` to the local input. The component never
manually parses dates, applies offsets, or maintains a second representation;
the schema defines both directions and the form keeps them synchronized.
## Focus into subforms ## Focus into subforms
The root `MutationForm` or `LensForm` represents the complete schema. UI The root `MutationForm` or `LensForm` represents the complete schema. UI
@@ -308,6 +190,16 @@ const contactForm = Form.focusObjectOn(form, "contact")
const emailField = Form.focusObjectOn(contactForm, "email") const emailField = Form.focusObjectOn(contactForm, "email")
``` ```
Form focus helpers also have a dual API. Pass only the key or index to get a curried function. Curried helpers can be chained
with `pipe` to focus through a deeper path:
```tsx
const emailField = form.pipe(
Form.focusObjectOn("contact"),
Form.focusObjectOn("email"),
)
```
`displayNameField`, `ageField`, and `emailField` can each be passed to the input `displayNameField`, `ageField`, and `emailField` can each be passed to the input
component responsible for that field. Focusing can also be repeated: component responsible for that field. Focusing can also be repeated:
`contactForm` represents the complete contact section, and `emailField` `contactForm` represents the complete contact section, and `emailField`
@@ -463,3 +355,121 @@ They expose the schema pipeline as reactive Lenses and Views:
The form model itself is Effect code. Effect View adds the React-facing hooks, The form model itself is Effect code. Effect View adds the React-facing hooks,
including `Form.useInput` and `Form.useOptionalInput`, while your own component including `Form.useInput` and `Form.useOptionalInput`, while your own component
library remains responsible for rendering controls and layout. library remains responsible for rendering controls and layout.
## Example: local date input, UTC domain value
The encoded/decoded distinction is especially useful for dates. An HTML
`datetime-local` input produces a wall-clock string such as
`"2026-07-22T14:30"`. It contains no time-zone or UTC offset, while application
state and APIs usually need an unambiguous UTC instant.
A schema can own that entire conversion. The following schema interprets the
input in the current time zone and decodes it to `DateTime.Utc`:
```tsx title="src/DateTimeUtcFromZonedInput.ts"
import { DateTime, Effect, Option, ParseResult, Schema } from "effect"
class DateTimeUtcFromZoned extends Schema.transformOrFail(
Schema.DateTimeZonedFromSelf,
Schema.DateTimeUtcFromSelf,
{
strict: true,
decode: (input) => ParseResult.succeed(DateTime.toUtc(input)),
encode: DateTime.setZoneCurrent,
},
) {}
export class DateTimeUtcFromZonedInput extends Schema.transformOrFail(
Schema.String,
DateTimeUtcFromZoned,
{
strict: true,
decode: (input, _options, ast) =>
Effect.flatMap(DateTime.CurrentTimeZone, (timeZone) =>
Option.match(
DateTime.makeZoned(input, {
timeZone,
adjustForTimeZone: true,
}),
{
onSome: ParseResult.succeed,
onNone: () =>
ParseResult.fail(
new ParseResult.Type(
ast,
input,
"Enter a valid date and time",
),
),
},
),
),
encode: (value) =>
ParseResult.succeed(
DateTime.formatIsoZoned(value).slice(0, 16),
),
},
) {}
```
Use it like any other field schema. The form and input work with a string, but
the mutation receives UTC:
```tsx
import { DateTime, Effect, Schema } from "effect"
import { Component, Form, MutationForm, View } from "effect-view"
import { DateTimeUtcFromZonedInput } from "./DateTimeUtcFromZonedInput"
const AppointmentSchema = Schema.Struct({
startsAt: DateTimeUtcFromZonedInput,
})
const AppointmentView = Component.make("Appointment")(function* () {
const [form, startsAtField] = yield* Component.useOnMount(() =>
Effect.gen(function* () {
const form = yield* MutationForm.service({
schema: AppointmentSchema,
initialEncodedValue: { startsAt: "" },
f: ([appointment]) =>
Effect.log(
`Saving ${DateTime.formatIso(appointment.startsAt)}`,
),
})
return [form, Form.focusObjectOn(form, "startsAt")] as const
}),
)
const startsAt = yield* Form.useInput(startsAtField)
const [canCommit] = yield* View.useAll([form.canCommit])
const runPromise = yield* Component.useRunPromise()
return (
<form
onSubmit={(event) => {
event.preventDefault()
void runPromise(form.submit)
}}
>
<input
type="datetime-local"
value={startsAt.value}
onChange={(event) =>
startsAt.setValue(event.currentTarget.value)
}
/>
<button disabled={!canCommit}>Save appointment</button>
</form>
)
})
```
The schema requires `DateTime.CurrentTimeZone`; provide
`DateTime.layerCurrentZoneLocal` in the application runtime. If the current
zone is `Europe/Paris`, for example, entering `2026-07-22T14:30` decodes to the
UTC instant `2026-07-22T12:30:00.000Z`.
Encoding works in the other direction. A `LensForm` whose target contains that
UTC instant presents `2026-07-22T14:30` to the local input. The component never
manually parses dates, applies offsets, or maintains a second representation;
the schema defines both directions and the form keeps them synchronized.
+23
View File
@@ -219,6 +219,11 @@ interface UserProfile {
readonly name: string readonly name: string
readonly email: string readonly email: string
readonly role: string readonly role: string
readonly contact: {
readonly address: {
readonly city: string
}
}
} }
class ProfileState extends Context.Service< class ProfileState extends Context.Service<
@@ -237,6 +242,11 @@ class ProfileState extends Context.Service<
name: "", name: "",
email: "", email: "",
role: "reader", role: "reader",
contact: {
address: {
city: "",
},
},
}), }),
) )
const name = Lens.focusObjectOn(profile, "name") const name = Lens.focusObjectOn(profile, "name")
@@ -269,6 +279,19 @@ Updating the focused `name` Lens through `Lens.useState` updates the parent
`profile` Lens. The focused `role` Lens is only read, so it stays on the simpler `profile` Lens. The focused `role` Lens is only read, so it stays on the simpler
`View.useAll` path. `View.useAll` path.
Lens focus helpers have a dual API. They can be called in data-first form, such
as `Lens.focusObjectOn(profile, "name")`, or in curried form with only the key
or index. The curried form composes naturally with `pipe` for deeper paths:
```tsx
// profileLens is a Lens.Lens<UserProfile>
const cityLens = profileLens.pipe(
Lens.focusObjectOn("contact"),
Lens.focusObjectOn("address"),
Lens.focusObjectOn("city"),
)
```
For focusing into nested state, deriving lenses, custom write behavior, and the For focusing into nested state, deriving lenses, custom write behavior, and the
complete API, refer to the complete API, refer to the
[`effect-lens` documentation](https://www.npmjs.com/package/effect-lens/v/beta). [`effect-lens` documentation](https://www.npmjs.com/package/effect-lens/v/beta).