Skip to content

Binding & Validation

Using data binding with Astro Forms

View Markdown

With Astro Forms you can easily create forms with data binding and validation. It also provides a simple way to handle form submissions and preserve state between postbacks.

Astro Forms supports two-way data binding. This means that you can bind a form field to a property on Bind instance and the value of the property will be updated when the field changes and vice versa.

---
import { Bind, BindForm, BButton, BInput } from "@astro-utils/forms/forms.js";
import Layout from "../layouts/Layout.astro";
type Form = {
name: string;
age: number;
}
const bind = Bind<Form>();
let showSubmitText = '';
function formSubmit(){
showSubmitText = `Your name is ${bind.name}; you are ${bind.age} years old.`;
bind.age++;
}
---
<Layout>
<BindForm {bind}>
{showSubmitText}
<label for="name">What is your name?</label>
<BInput id="name" type="text" name="name" maxlength={20} required/>
<label for="age">Enter age</label>
<BInput id="age" type="int" name="age" min={10} required/>
<BButton onClick={formSubmit} whenFormOK>Submit</BButton>
</BindForm>
</Layout>

In this example, the Bind instance is bound to the form fields.

  • When the user changes the value of the name or age fields and submits, the Bind instance is updated.
  • The formSubmit function will be called when the user clicks the Submit button and the form is valid.
  • After formSubmit the age property will be incremented and the showSubmitText will be updated.

HTML constraints such as required and minlength are checked by the browser and server. A custom validator handles application rules. Try admin to see a server validation error; Alex succeeds.

src/pages/choose-name.astro
---
import { BButton, BInput, Bind, BindForm, FormErrors } from '@astro-utils/forms/forms.js';
import Layout from '../layouts/Layout.astro';
const bind = Bind({ name: '' });
let saved = false;
function validateName(value: string) {
if (['admin', 'support'].includes(value.trim().toLowerCase())) {
return { error: 'Choose a name other than admin or support.' };
}
}
function save() {
saved = true;
}
---
<Layout>
<span slot="title">Choose a name</span>
<BindForm {bind}>
<FormErrors />
<label for="name">Display name</label>
<BInput id="name" name="name" required minlength={2} validate={validateName} />
<BButton onClick={save} whenFormOK>Save name</BButton>
{saved && <p role="status">Name accepted: {bind.name}</p>}
</BindForm>
</Layout>

Return { error } to reject a value, or return nothing to accept it. FormErrors renders the feedback; whenFormOK prevents save from running on invalid input. See the validation contract.

Astro Forms also supports view state. This means that the values of the form fields will be preserved between postbacks.

---
import { Bind, BindForm, BButton } from '@astro-utils/forms/forms.js';
import Layout from '../layouts/Layout.astro';
type Form = {
counter: number;
}
const bind = Bind<Form>({counter: 0});
function incCounter(){
bind.counter++;
}
---
<Layout>
<BindForm {bind}>
Counter: {bind.counter}
<BButton onClick={incCounter}>Increase counter</BButton>
</BindForm>
</Layout>

What happens here is that the counter property of the Bind instance will be incremented every time the user clicks the Increase counter button.

The Bind state persists between postbacks by storing compressed and encrypted view state on the client.

The state of the Bind instance must be serializable, but it can contain more than plain JSON values.

You can use any valid SuperJSON value in the Bind instance. This includes Date, Map, URL, Set, RegExp, BigInt, undefined are supported.

You can use state per BButton component. You can also change the BButton props easily each time the button is clicked.

Special props:

  • state: any - store state per button
  • extra: any - store extra data per button (cannot be changed in the onClick function)
  • innerText: string - the text of the button (overrides the children)
  • innerHTML: string - the HTML of the button (overrides the children)
  • remove: boolean - remove the button from the HTML
  • reset(): function - reset the button to the initial state
  • defaults: any - the default state of the button
---
import { BButton, Bind, BindForm } from '@astro-utils/forms/forms.js';
import Layout from '../layouts/Layout.astro';
const colors = ['red', 'green', 'blue', 'yellow', 'purple', 'orange', 'pink', 'black', 'white', 'gray'];
function changeColor() {
this.state.color++;
if (this.state.color >= colors.length) {
this.state.color = this.extra;
}
this.style = `color: ${colors[this.state.color]}`;
}
---
<Layout>
<BindForm>
{
colors.map((_, i) => (
<BButton onClick={changeColor} extra={i} state={{ color: i }}>
Button {i}
</BButton>
))
}
</BindForm>
</Layout>

In this example, the changeColor will change the button color each time it is clicked to another color in the colors array.

If the button is clicked more times than the length of the colors array, it starts from the beginning.