Bindings
Bindings connect form state to UI components. They encapsulate the logic for creating props that can be spread onto input elements.
The @mobx-sentinel/form package provides only the API for creating bindings—it does not include any pre-built binding implementations. You have two options:
- Use
@mobx-sentinel/react- Pre-built bindings for React components (InputBinding, CheckBoxBinding, SubmitButtonBinding, etc.) - Build your own bindings - Implement custom bindings for your framework or specific use cases
Creating Binding Classes and Binding Examples demonstrate how to create custom bindings. The examples are based on the actual implementations in @mobx-sentinel/react.
Using Bindings
Section titled “Using Bindings”Bindings are cached and reused. The same binding constructor with the same binding key returns the same instance. Configuration can be updated on subsequent calls while maintaining the same binding instance.
The binding key consists of three components:
- Binding class — e.g., InputBinding, LabelBinding, SubmitButtonBinding
- Subject of binding — a single field, multiple fields, or the entire form
- User-specified key (optional) — the
cacheKeyproperty in configuration
Use form.bind() to create binding props and spread them directly into components:
const model = new User();const form = Form.get(model);
{/* Bind to a single field */}<input {...form.bind('email', InputBinding, { getter: () => model.email, setter: (value) => model.email = value,})} />
{/* Bind with additional configuration */}<input {...form.bind('password', InputBinding, { type: 'password', getter: () => model.password, setter: (value) => model.password = value,})} />
{/* Bind to multiple fields */}<label {...form.bind(['email', 'password'], LabelBinding)}>Credentials</label>
{/* Bind to the form */}<button {...form.bind(SubmitButtonBinding)}>Submit</button>Element IDs
Section titled “Element IDs”A label and its control find each other through an id, so every field carries one: field.stableId, which the standard bindings put in id, htmlFor, and a radio group's name. The form has one too, form.stableId.
A form's stable id starts as its own identity, form.id, and until it is assigned another, each field's is its own identity too, field.id: unique on the page, but different in every process. That is all a client-rendered app needs.
Server-side rendering needs more, because the markup is built in one process and hydrated in another. Assign the form a stable id that both arrive at, and its fields' stable ids build on it:
form.stableId = `invoice-form-${invoice.id}`;form.getField('customerEmail').stableId; // "invoice-form-42:customerEmail"| Until assigned | After form.stableId = id |
|
|---|---|---|
form.stableId |
form.id |
<id> |
field.stableId |
field.id |
<id>:<field name> |
For anything that reaches the DOM, use stableId rather than id, so it keeps matching once the form's stable id is assigned.
The id is used as is, so nothing else on the page may use it. In React, useFormSSR from @mobx-sentinel/react assigns useId(), which guarantees that: no other call returns the same value, so nothing in the model has to be involved. It gives the form its own id back when the component unmounts. A key from your own data works too, as long as it is unique on the page. Assign it before binding: bindings read the stable id as they are called, and it is not reactive.
A sub-form is a separate instance with its own stable id. Assign it where the sub-form is rendered, so no component has to know what its ancestors did.
A field's stable id contains :, which is fine for htmlFor, aria-* attributes and document.getElementById(). In a CSS selector, escape it with CSS.escape().