Form / foldkit-form
Example
ts
import { Schema } from 'effect'
import { Bundle } from 'foldkit-bundle'
import { Entity } from 'foldkit-entity'
import { Form } from 'foldkit-form'
const Post = Entity.define(
'Post',
Schema.Struct({
id: Schema.String,
title: Schema.String.check(Schema.isMinLength(1)).annotate({ title: 'Title' }),
}),
)
// The operation's input, declared once. Give the same value to the operation.
const RenameInput = Schema.Struct({ id: Schema.String, title: Post.fields.title.schema })
const Rename = Form.make('Rename', Entity.input(Post, RenameInput))
// Place it: a Model field and a Message variant of the page.
const Page = Bundle.compose({ saved: Schema.Array(RenameInput) }).pipe(
Bundle.withChild('rename', Rename.bundle, {
// `submitted.value` is a decoded RenameInput. What it means is the page's.
onOut: submitted => model => ({
model: { ...model, saved: [...model.saved, submitted.value] },
}),
}),
)
const RenameForm = Page.children.renameForm.maketakes the name and anEntity.input. It resolves a control per key and throws if it cannot. A form that writes no Entity of its own (a sign-up, a search, a login) takesForm.input(schema), an input over an Entity defined from that schema:Form.make('Search', Form.input(SearchInput)). A view must also supply a renderer for each control kind, including custom kinds.Rename.bundleis an ordinary Bundle.onOutis required, so a submit is never dropped by omission.Rename.Messagebuilds the form's Messages for your view to dispatch:Changed({ key, value }),Blurred({ key }),Submitted(),Reset(), andValidatedAll()(see Validation).Rename.controlslists the keys in the input's order, each with itscontrol,label,description,required, and the Entitymember.Rename.field(model.rename, key)reads one key's state asField<Draft>, for a view that walkscontrols;model.rename.fields.titleis the same value typed to its key.
Run one edit through the parent
Continuing the example above, an assembly initializes the slice and routes its wrapped Messages. This reducer-level example needs no renderer:
ts
const { placements } = Page
const update = placements.update(model => ({ model }))
let model = placements.initial({ saved: [] }).model
model = update(model, Page.Message.GotRenameMessage({
message: Rename.Message.Changed({ key: 'title', value: 'Hello' }),
})).model
model = update(model, Page.Message.GotRenameMessage({
message: Rename.Message.Submitted(),
})).model
// model.saved contains the decoded input, with title "Hello" and id "".Here a plain Schema.String allows the empty id. For editing a real row, fill
the form with that row's id first; add an input-schema constraint if an empty
id is invalid for your operation. onOut stores the value locally in this
example. It does not save to a server. Forms with asynchronous checks also need
the runtime to execute their returned Commands.