# Binding & Validation

Using data binding with Astro Forms

Source: https://withastro-utils.github.io/docs/guides/forms/data-binding/

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.

## Data Binding

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.

### Submit a profile

### src/pages/profile.astro

```astro title="src/pages/profile.astro"
---
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.

## Validate a reserved name

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.

```astro title="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](https://withastro-utils.github.io/docs/reference/forms/data-binding/index.md#custom-validation).

## View State

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

### Keep a counter between submissions

### src/pages/counter.astro

```astro title="src/pages/counter.astro"
---
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.

### Valid values

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

You can use any valid [SuperJSON](https://github.com/blitz-js/superjson) value in the `Bind` instance.
This includes `Date`, `Map`, `URL`, `Set`, `RegExp`, `BigInt`, `undefined` are supported.

### Button State

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

### src/pages/button-colors.astro

```astro title="src/pages/button-colors.astro"
---
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.
