---
area: "Learn"
parent: "Forms chapter"
url: "https://unpoly.com/learn/reactive-server-forms"
---
<nav aria-label="Unpoly docs">[All docs](https://unpoly.com/index.md) · [Forms chapter](https://unpoly.com/learn/forms.md)</nav>

# Reactive server forms

Unpoly lets you build forms where one field re-renders other parts of the form when it changes. The new form state is rendered on the server, with your regular HTML templates and no additional JavaScript.

Common use cases for such forms are:

- Picking a `<select>` value fetches options for another `<select>`.
- Changing a field makes dependent form sections appear or disappear.
- Typing into a field previews the effects of a submission, e.g. shows an order total.
- Picking a value shows related data, like existing bookings for a selected customer.

All of these are implemented with the [`[up-validate]`](https://unpoly.com/api/up-validate.md) attribute.

> #### Tip
>
> For light, client-side effects like toggling visibility, no server request is required. See [Switching form state](https://unpoly.com/learn/switching-form-state.md).

<nav aria-label="Contents">

#### Contents

- [Declaring dependencies](#declaring-dependencies)
- [Rendering the form state on the server](#server)
- [Multiple dependent fragments](#multiple-dependent-fragments)
- [Preventing race conditions](#race-conditions)
- [Updating from JavaScript](#script)
- [Rendering from other URLs](#urls)

</nav>

## Declaring dependencies {#declaring-dependencies}

This is a form to purchase postage for international parcels:

<figure>
<img src="https://unpoly.com/images/api/form-with-dependent-fields.svg" alt="Mockup of a form to buy postage">
<figcaption>

The form has a select for the continent (Europe), a select for the country (Switzerland), a text field for the weight in grams (3500), and the price (23,00 €) shown as text, not as a field. A "Buy stamps" button submits the form.

</figcaption>
</figure>

The fields in this form have many dependencies between them:

- When the continent is changed, the country select must update to list countries from the selected continent.
- When the country is changed, the price preview must update.
- When the parcel weight is changed, the price preview must update.

We can implement this form with three [`[up-validate]`](https://unpoly.com/api/up-validate.md) attributes and no additional JavaScript:

```html
<form method="post" action="/purchases">
  <fieldset>
    <label for="continent">Continent</label>
    <select name="continent" id="continent" up-validate="#country">...</select>
  </fieldset>

  <fieldset>
    <label for="country">Country</label>
    <select name="country" id="country" up-validate="#price">...</select>
  </fieldset>

  <fieldset>
    <label for="weight">Weight</label>
    <input name="weight" id="weight" up-validate="#price"> kg
  </fieldset>

  <fieldset>
    <label for="price">Price</label>
    <output id="price">23 €</output>
  </fieldset>

  <button>Buy stamps</button>
</form>
```

When a field is changed, Unpoly submits the form with an additional [`X-Up-Validate`](https://unpoly.com/api/X-Up-Validate.md) HTTP header. Upon seeing this header, the server renders a new form state from the form values in the request parameters.

When the server responds with the re-rendered form, Unpoly updates only the [target](https://unpoly.com/learn/targeting-fragments.md) from the changed field's [`[up-validate]`](https://unpoly.com/api/up-validate.md) attribute. For instance, when the continent is changed, only the country select is updated. Everything else in the form keeps its state, including unsaved input in other fields.

## Rendering the form state on the server {#server}

The server needs to handle a validation request differently from a regular submission. When it sees an [`X-Up-Validate`](https://unpoly.com/api/X-Up-Validate.md) header, it must not save the submitted data. Instead it renders the form again from the request parameters, with dependent elements reflecting the new values.

In a Ruby on Rails app this would look like this:

```ruby
class PurchasesController < ApplicationController

  def create
    @purchase = Purchase.new(purchase_params)
    if request.headers['X-Up-Validate']
      @purchase.validate
      render 'form'
    elsif @purchase.save
      redirect_to @purchase
    else
      render 'form', status: :unprocessable_entity
    end
  end

end
```

The controller validates `@purchase` without saving it, so the form can also show error messages for the current values. The template for `form` renders the country options and the price from the `@purchase` object. It does not need to know which field changed, since Unpoly extracts only the targeted fragment from the response.

The examples use Ruby on Rails, but work similarly with any other backend.

See [`[up-validate]`](https://unpoly.com/api/up-validate.md#backend-protocol) for the request and response in detail.

## Multiple dependent fragments {#multiple-dependent-fragments}

A field with [`[up-validate]`](https://unpoly.com/api/up-validate.md) may [update multiple fragments](https://unpoly.com/learn/targeting-fragments.md#multiple) by separating their target selectors with a comma.

For instance, when the user changes the continent, the following would update the price preview in addition to the country select:

```html
<select name="continent" up-validate="#country, #price">
  ...
</select>
```

To update another fragment _in addition_ to the field's [form group](https://unpoly.com/api/up-form-group.md), include the group in the target list.
You can refer to the changed field as [`:origin`](https://unpoly.com/api/origin.md):

```html
<fieldset>
  <select name="continent" up-validate="fieldset:has(:origin), #country, #price">
    ...
  </select>
</fieldset>
```

## Preventing race conditions {#race-conditions}

Users can change fields faster than your server responds. Unpoly keeps the form consistent with these rules:

- **One request at a time.** Each form has at most one validation request in flight. Later changes wait until it has loaded.
- **Batched updates.** Changes that waited are [sent together](https://unpoly.com/api/up.validate.md#batching), in a single request for all their targets. When one target contains another, only the outer one is requested.
- **Submitting wins.** When the user submits while a validation is loading, the validation is [aborted](https://unpoly.com/learn/aborting-requests.md). This happens when the submission updates the form or an element around it, like the [main fragment](https://unpoly.com/api/main.md).

Let's walk through a challenging scenario using the [postage form example](#declaring-dependencies) above:

| User input | Network | Form changes |
| --- | --- | --- |
| Selects a continent | Request 1 for the country select |  |
| Enters a parcel weight | _waits for request 1_ |  |
| Changes the continent again | _waits for request 1_ |  |
|  | Request 1 loads | Country select updated |
|  | Request 2 for country select and price |  |
|  | Request 2 loads | Country select and price updated |

Once the last response is processed, all fields and the price preview show consistent values.

### Disabling fields during validation {#disabling-fields-during-validation}

If you prefer to completely prevent user input during validation, give the form an `[up-watch-disable]` attribute. This disables all form fields while validation requests are in flight:

```html
<form method="post" action="/purchases" up-watch-disable>
  ...
</form>
```

You may also set `[up-watch-disable]` on individual fields, or on any element that contains fields.

See [disabling fields while working](https://unpoly.com/learn/watch-options.md#disabling) for details.

## Updating from JavaScript {#script}

To re-render a fragment of the form from your own code, pass it to [`up.validate()`](https://unpoly.com/api/up.validate.md):

```js
up.validate('#price')
```

This submits the form with an [`X-Up-Validate`](https://unpoly.com/api/X-Up-Validate.md) header, like a change to an [`[up-validate]`](https://unpoly.com/api/up-validate.md) field would, and updates the `#price` element with the response. You can combine [`[up-validate]`](https://unpoly.com/api/up-validate.md) attributes and [`up.validate()`](https://unpoly.com/api/up.validate.md) calls within the same form. Their updates are [batched together](https://unpoly.com/api/up.validate.md#batching) and follow the same rules for [preventing race conditions](#race-conditions).

## Rendering from other URLs {#urls}

By default, validation requests use the `[method]` and `[action]` attributes from the form element.

You can render content from another server endpoint by setting an [`[up-validate-url]`](https://unpoly.com/api/up-validate.md#up-validate-url) attribute on a form or field:

```html
<form method="post" action="/order">
  <input name="quantity" up-validate="#preview" up-validate-url="/preview-order">

  <div id="preview">
    Order total: €190
  </div>
</form>
```

Multiple validations to the same URL are [batched together](https://unpoly.com/api/up.validate.md#batching).

<nav aria-label="Reading order">

- [Previous: Validating forms](https://unpoly.com/learn/validation.md)
- [Next: Switching form state](https://unpoly.com/learn/switching-form-state.md)

</nav>

