text: # Building forms with FormState
  
  ## Basic
  
  When using `<FormState>`, you express your form's component tree as a function of the generated field state objects provided by `<FormState />` into its `children` function as `formDetails`.
  
  ```typescript
  interface FormDetails<Fields> {
    fields: FieldDescriptors<Fields>;
    dirty: boolean;
    valid: boolean;
    submitting: boolean;
    errors: RemoteError[];
    reset(): void;
    submit(): void;
  }
  ```
  
  The `formDetails` object passed into `children` contains a `fields` dictionary, which includes all the state and handlers you need to render your form.
  
  ```typescript
  interface FieldState<Value> {
    name: string;
    initialValue: Value;
    value: Value;
    dirty: boolean;
    error?: any;
    onBlur(): void;
  }
  ```
  
  The simplest way to use `<FormState />` is to pass it some `initialValues` and render some `input`s in its `children` render prop.
  
  ```typescript
  import FormState from '@shopify/react-form-state';
  
  function MyComponent() {
    return (
      <FormState
        initialValues={{
          title: 'Cool title',
          description: 'Cool product',
        }}
      >
        {formDetails => {
          const {fields} = formDetails;
          const {title, description} = fields;
  
          return (
            <form>
              <label htmlFor={title.name}>Title</label>
              <input
                {...title}
                id={title.name}
                onChange={({currentTarget}) => {
                  // our onChange expects just a value, no event needed
                  title.onChange(currentTarget.value);
                }}
              />
  
              <label htmlFor={description.name}>Title</label>
              <input
                {...description}
                id={title.name}
                onChange={({currentTarget}) => {
                  description.onChange(currentTarget.value);
                }}
              />
            </form>
          );
        }}
      </FormState>
    );
  }
  ```
  
  ## Reducing boilerplate with custom inputs
  
  The previous example has a fair amount of repetition around passing the right handlers to the right inputs. It also doesn't handle rendering errors. We can eliminate this boilerplate by using a custom component with props which match our field descriptor objects.
  
  [@shopify/polaris](https://polaris.shopify.com/components/forms/text-field)'s `<TextField />` component expects props that match our API. So, using `<TextField />` and ES6 [spread](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) syntax we can greatly simplify our forms. The Polaris inputs also support `error` props, allowing us to surface error messages to our users at the field level once we set up validation.
  
  ```typescript
  import {TextField} from '@shopify/polaris';
  import FormState from '@shopify/react-form-state';
  
  function MyComponent() {
    return (
      <FormState
        initialValues={{
          title: 'Cool title',
          description: 'Cool product',
        }}
      >
        {formDetails => {
          const {fields} = formDetails;
  
          return (
            <form>
              <TextField label="Title" {...fields.title} />
              <TextField label="Description" {...fields.description} />
            </form>
          );
        }}
      </FormState>
    );
  }
  ```
  
  There is also nothing to stop you from using your own custom inputs.
  
  ## Submission
  
  Forms usually need some mechanism of submission. `<FormState />` is not opinionated about how this is done, but does provide a built-in mechanism for managing loading state and updating based on errors from the submission process. Most of this is done through the `onSubmit` property.
  
  ```typescript
    onSubmit={async ({fields}: FormData<Fields>) => {
      const result = await updateProduct(fields);
  
      if (isErrorResult(result)) {
        return result.errors;
      }
    }}
  ```
  
  When given an [async](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) function (or one that returns a [promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function), the `children` function will be rerendered with `submitting: true` until the promise resolves or rejects. Once it has returned the function will be re-rendered with `submitting: false` and `errors` either `[]` (if the promise resolved with no value), or with an array of `{field?: string | string[], error: string}` if the promise resolved with an error list. Any errors with a `field` value will also be propagated down to matching fields.
  
  ```typescript
  // api.ts
  
  const fakeErrorResult = {error: [{message: 'api is not real'}]};
  
  // simple async function that fakes a failing api call
  export function fakeApiCall(data: any) {
    return new Promise(resolve => {
      setTimeout(() => {
        resolve(fakeErrorResult);
      }, 2000);
    });
  }
  ```
  
  ```typescript
  // MyPage.ts
  import {fakeApiCall} from './api';
  import {TextField, Button, Banner, FormLayout, Spinner} from '@shopify/polaris';
  import FormState from '@shopify/react-form-state';
  
  export function MyPage() {
    return (
      <FormState
        initialValues={{email: '', password: ''}}
        onSubmit={async () => {
          const results = await fakeApiCall();
          if (results.error) {
            return results.error;
          }
        }}
      >
        {({fields, submitting, errors, submit, valid}) => {
          return (
            <form onSubmit={submit}>
              {submitting && <Spinner size="large" color="teal" />}
  
              {!valid && (
                <Banner status="critical">
                  <ul>
                    {errors.map(error => (
                      <li key={`${error.message}${error.field}`}>
                        {error.message}
                      </li>
                    ))}
                  </ul>
                </Banner>
              )}
  
              <FormLayout>
                <TextField label="Email" {...fields.email} />
                <TextField label="Password" {...fields.password} />
              </FormLayout>
  
              <Button primary type="submit" disabled={submitting}>
                Save
              </Button>
            </form>
          );
        }}
      </FormState>
    );
  }
  ```
  
  ## Resetting
  
  Most forms need to expose a way to reset all fields to their initial values. The `reset()` function passed into `children()` allows you to easily make this available to a user.
  
  ```typescript
  // MyPage.ts
  import {TextField, Button} from '@shopify/polaris';
  import FormState from '@shopify/react-form-state';
  
  export function MyPage() {
    return (
      <FormState
        initialValues={{email: '', password: ''}}
      >
        {({fields, reset, dirty}) => {
          return (
            <form>
              <FormLayout>
                <TextField label="Email" {...fields.email} />
                <TextField label="Password" {...fields.password} />
              </FormLayout>
  
              <Button
                onClick={reset}
                disabled={!dirty}
              >
                Reset
              </Button>
            </form>
          );
        }}
      </FormState>
    );
  ```
  
  ## Dirty Tracking
  
  Often you want to change some UI behavior based on whether a form has changed from its initial values. `<FormState />` supports this both on the form and individual field level.
  
  ```typescript
  // MyPage.ts
  import {TextField, Button} from '@shopify/polaris';
  import FormState from '@shopify/react-form-state';
  
  export function MyPage() {
    return (
      <FormState
        initialValues={{email: '', password: ''}}
      >
        {({fields, dirty}) => {
          const dirtyMarkup = fields.password.dirty && (
            <p>you have edited this</p>
          );
  
          return (
            <form>
              <FormLayout>
                <TextField label="Email" {...fields.email} />
                <TextField label="Password" {...fields.password} />
                {dirtyMarkup}
              </FormLayout>
  
              <Button
                primary
                type="submit"
                disabled={!dirty}
              >
                Save
              </Button>
            </form>
          );
        }}
      </FormState>
    );
  ```
  
  # Validation
  
  `<FormState />` supports client-side validation through the `validators` prop.
  
  ```typescript
  export type ValidatorDictionary<Fields> = {
    [FieldPath in keyof Fields]: MaybeArray<
      ValidationFunction<Fields[FieldPath], Fields>
    >
  };
  
  interface ValidationFunction<Value, Fields> {
    (value: Value, fields: FieldStates<Fields>): any;
  }
  ```
  
  To define validators use the `validators` prop and pass it a partial dictionary of validation functions.
  
  ```typescript
    validators={{
      title(input) {
        if (input === 'foo') {
          return 'title cannot be foo';
        }
      },
    }}
  ```
  
  To run multiple functions on the same field you can also use an array of functions.
  
  ```typescript
    validators={{
      password: [
        (input) => {
          if (input.length > 20) {
            return 'too big';
          }
        },
        (input) => {
          if (input.length < 3) {
            return 'too small';
          }
        },
    ]}}
  ```
  
  To run multiple functions on an inner field of a nested field you can use [validateNested](validators.md#validatenested).
  
  ```typescript
    validators={{
      person: validateNested({
        age: [
          (input) => {
            if (input < 16) {
              return 'too young';
            }
          },
          (input) => {
            if (input > 200) {
              return 'too old';
            }
          },
        ]
      })
  ```
  
  `<FormState />` generates handlers that follow the [Polaris form validation guidelines](https://polaris.shopify.com/patterns/error-messages#section-form-validation). When you blur an input, or when you change it and it already has an error, any defined validators for that field will be invoked.
  
  ```typescript
  import {TextField} from '@shopify/polaris';
  import FormState, {validators} from '@shopify/react-form-state';
  
  function MyComponent() {
    return (
      <FormState
        initialValues={{
          title: 'Cool title',
          description: 'Cool product',
          quantity: 0,
        }}
        validators={{
          title(input) {
            if (input.length > 10) {
              return 'That title is too long';
            }
          },
          quantity() {
            if (!/[^0-9.,]/g.test(input)) {
              return 'Quantity must be a number';
            }
          },
        }}
      >
        {formDetails => {
          const {fields} = formDetails;
  
          return (
            <form>
              <TextField label="Title" {...fields.title} />
              <TextField label="Description" {...fields.description} />
              <TextField label="Quantity" {...fields.quantity} />
            </form>
          );
        }}
      </FormState>
    );
  }
  ```
  
  ### validateOnSubmit
  
  You can configure `<FormState />` to run all validators on the form before executing `onSubmit` by passing it the `validateOnSubmit` prop. If any of the validators return an error, the submit is cancelled and `onSubmit` is not run. Any errors discovered will be available on the `errors` object.
  
  ```typescript
  import {Banner, Button, ExceptionList, Form, TextField} from '@shopify/polaris';
  import FormState, {validators} from '@shopify/react-form-state';
  
  function MyComponent() {
    return (
      <FormState
        validateOnSubmit
        initialValues={{
          title: 'Cool title',
        }}
        validators={{
          title(input) {
            if (input.length > 10) {
              return 'That title is too long';
            }
          },
        }}
        onSubmit={() => {
          console.log('I will not be run if title is too long');
        }}
      >
        {formDetails => {
          const {errors, fields, submit} = formDetails;
  
          const errorBanner = Boolean(errors.length) && (
            <Banner status="critical" title={`${errors.length} Errors`}>
              <ExceptionList
                items={errors.map(({message: description}) => ({description}))}
              />
            </Banner>
          );
  
          return (
            <>
              {errorBanner}
              <Form onSubmit={submit}>
                <TextField label="Title" {...fields.title} />
                <Button type="submit">Submit</Button>
              </Form>
            </>
          );
        }}
      </FormState>
    );
  }
  ```
  
  In addition to being propagated down to their respective `fields`, these client-side validation errors can be accessed together at the top level via the `errors` array, in the same way as your remote errors (returned from `onSubmit`) and external errors (see below).
  
  To learn more about building validators, and the built in functions exposed by this package, check out the [validators guide](validators.md).
  
  ## External errors
  
  You can use the `externalErrors` prop to supply `<FormState />` with external errors. This is useful for displaying errors that occur outside of the normal form submit flow. These errors will be available alongside regular errors via the `errors` array.
  
  ```typescript
  class MyComponent extends React.Component {
   state = {
     externalErrors: [{message: 'Example error', field: 'foo'}]
   }
    render() {
     const {externalErrors} = this.state;
      return (
       <FormState
         initialValues={{foo: ''}}
         externalErrors={externalErrors}
         onSubmit={async (formDetails) => {
           const response = await submitFormMutation(formDetails);
             if (response.errors) {
                // we still need to clear the errors ourselves because formstate
               // doesn't know what they represent or when to clear them
                this.setState({externalErrors: null});
                return response.errors;
            }
         }}
       >
         {({fields, errors}) => {
            return (
              <Banner>
               {/* the externalErrors get seamlessly added to the errors array */}
                {errors.map(error => <li>{error.message}</li>)}
              </Banner>
                {/* and passed down to matching fields */}
               <TextField {...fields.foo} />
            )
         }}
       </FormState>
     )
   }
  }
  ```
  
  ## Compound fields
  
  The default API for generating handlers with `<FormState />` is very simple, but real world apps are often very complex. You will often have to represent editing many resources with one form. This can mean needing support for arrays of fields, fields that are nested objects, or both. `<FormState />` allows passing these complex values into `initialValues` and having your own special sub-components that use the handlers, but for most cases it's recommended to use the sub-components `<FormState.List />` and `<FormState.Nested />`.
  
  ### `<FormState.Nested />`
  
  Sometimes your data might be best represented using a sub-object.
  
  ```typescript
  initialValues={{
    firstVariant: {
      option: 'size',
      value: 'enormous',
      price: '2.00',
      sku: 'mc23',
    }
    /* other fields */
  }}
  ```
  
  Passing this directly into `<FormState />` will generate one field description object for the entire object, just like any other field.
  
  This might work for some usecases if you have some kind of custom component that you just want to pass an updater to, but this can make collection values back for submission, validation, and other subtleties a pain to manage.
  
  In cases like this you can use `<FormState.Nested />` to generate individual field descriptors for the sub-fields.
  
  ```typescript
  // ProductPage.ts
  import {TextField} from '@shopify/polaris';
  import FormState from '@shopify/react-form-state';
  
  export function ProductPage() {
    return (
      <FormState
        initialValues={{
          name: '',
          description: '',
          firstVariant: {
            option: 'size',
            value: 'enormous',
            price: '2.00',
            sku: 'mc23',
          }
        }}
      >
      {({fields}) => {
        return (
          <form>
            <div>
              <TextField label="Name" {...fields.name} />
              <TextField label="Description" {...fields.description} />
  
              <FormState.Nested field={fields.firstVariant}>
                {(fields) => {
                  <TextField label="Option" {...fields.option} />
                  <TextField label="Value" {...fields.value} />
                  <TextField label="SKU" {...fields.sku} />
                  <TextField label="Price" {...fields.price} />
                }}
              </FormState.Nested>
            </div>
          </form>
        )
      }}
      </FormState>
    );
  }
  ```
  
  ### `<FormState.List />`
  
  Sometimes your data might be best represented using an array of objects.
  
  ```typescript
  initialValues={{
    variants: [{
      option: 'size',
      value: 'enormous',
      price: '2.00',
      sku: 'mc23',
    }, {
      option: 'size',
      value: 'teensy',
      price: '0.20',
      sku: 'mc25',
    }]
    /* other fields */
  }}
  ```
  
  Passing this directly into `<FormState />` will generate one field description object for the entire array, just like any other field.
  
  This might work for some usecases if you have some kind of custom component that you just want to pass an updater to, but this can make collecting values back for submission, validation, and other subtleties a pain to manage. It can also make adding and removing fields quite code intensive.
  
  In cases like this you can use `<FormState.List />` to generate individual field descriptors for the sub-fields of each object in the array. `<FormState.List />` will render the `children` function you give it once for each item in the `field` array you give it, passing in a `fields` object with the same type of signature as `<FormState.Nested />`.
  
  ```typescript
  // ProductPage.ts
  import {TextField} from '@shopify/polaris';
  import FormState from '@shopify/react-form-state';
  
  export function ProductPage() {
    return (
      <FormState
        initialValues={{
          name: '',
          description: '',
          variants: [{
            option: 'size',
            value: 'enormous',
            price: '2.00',
            sku: 'mc23',
          }, {
            option: 'size',
            value: 'teensy',
            price: '0.20',
            sku: 'mc25',
          }],
        }}
      >
      {({fields}) => {
        return (
          <form>
            <div>
              <TextField label="Name" {...fields.name} />
              <TextField label="Description" {...fields.description} />
  
              <FormState.List field={fields.variants}>
                {(fields) => {
                  <TextField label="Option" {...fields.option} />
                  <TextField label="Value" {...fields.value} />
                  <TextField label="SKU" {...fields.sku} />
                  <TextField label="Price" {...fields.price} />
                }}
              </FormState.List>
            </div>
          </form>
        )
      }}
      </FormState>
    );
  }
  ```
  
  By default `<List />` will use the `index` of your objects in the array as React `key`s. This is problematic if your array items could be re-ordered, so it is highly recommended you provide a `getChildKey` function. Your `getChildKey` will be passed each item in your array, and expects you to return a unique value that can be used to identify that item. This could be an `id` if that exists, or something else that makes sense in your usecase.
  
  For our example above, it makes sense to assume that each combination of `option` and `value` is unique.
  
  ```typescript
  <FormState.List
    field={fields.variants}
    getChildKey={(variant) => `${variant.option}-${variant.value}`}
  >
  ```
  
  ## Putting it all together
  
  The following example shows how you can use everything this documentation covered to build a full [Polaris](https://polaris.shopify.com) styled page.
  
  [Try it out in codesandbox](https://codesandbox.io/s/zx387x0vmx)
  
  ```typescript
  import React from 'react';
  import {
    Page,
    TextField,
    FormLayout,
    PageActions,
    Spinner,
    Banner,
    Stack,
    Card,
    Layout,
    ButtonGroup,
    Button,
    Form,
  } from '@shopify/polaris';
  import FormState, {
    validators,
    validateList,
    validateNested,
    arrayUtils,
  } from '@shopify/react-form-state';
  
  const {
    required,
    requiredString,
    numericString,
    nonNumericString,
    lengthMoreThan,
  } = validators;
  
  interface Variant {
    option: string;
    value: string;
    price: string;
  }
  
  interface Props {
    initialValues: {
      title: string;
      description: string;
      sku: string;
      quantity: string;
      firstVariant: Variant;
      variants: Variant[];
    };
  }
  
  function CreateProductPage({initialValues}: Props) {
    return (
      <FormState
        initialValues={initialValues}
        validators={{
          title: requiredString('Required'),
          quantity: numericString('Must be a number'),
          sku: lengthMoreThan(3, 'Must  be longer than 3 characters'),
          firstVariant: validateNested({
            option: required('required'),
            price: numericString('value must be numeric'),
          }),
          variants: validateList({
            option: nonNumericString('option must be nonNumeric'),
            price: numericString('value must be numeric'),
          }),
        }}
        onSubmit={({fields}) => {
          /*
            In a real project you would make some kind of asynchronous call here
            such as a graphQL mutation or a POST request, and return an array of errors if you encounter any.
          */
          return [{message: 'server error'}];
        }}
      >
        {formDetails => {
          const {fields, dirty, reset, submit, submitting, errors} = formDetails;
          const {
            title,
            description,
            sku,
            quantity,
            variants,
            firstVariant,
          } = fields;
  
          const submitAction = {
            content: 'Save',
            onAction: submit,
            disabled: !dirty || submitting,
            loading: submitting,
          };
  
          const resetAction = {
            content: 'Reset',
            onAction: reset,
            disabled: !dirty || submitting,
          };
  
          const errorBanner = errors.length > 0 && (
            <Banner status="critical">
              <ul>
                {errors.map(error => (
                  <li key={`${error.message}${error.field}`}>{error.message}</li>
                ))}
              </ul>
            </Banner>
          );
  
          function addVariant() {
            variants.onChange(
              arrayUtils.push(variants.value, {
                option: '',
                value: '',
                price: '0',
              }),
            );
          }
  
          return (
            <Form onSubmit={submit}>
              <Page
                title="Products"
                primaryAction={submitAction}
                secondaryActions={[resetAction]}
              >
                <Layout>
                  <Layout.Section>{errorBanner}</Layout.Section>
  
                  <Layout.Section>
                    <Stack distribution="center">
                      {submitting && <Spinner />}
                    </Stack>
  
                    <Card>
                      <Card.Section>
                        <FormLayout>
                          <TextField label="Title" {...title} />
                          <TextField label="Description" {...description} />
                          <TextField label="SKU" {...sku} />
                          <TextField
                            type="number"
                            label="Quantity"
                            {...quantity}
                          />
                        </FormLayout>
                      </Card.Section>
                    </Card>
                  </Layout.Section>
  
                  <Layout.Section>
                    <Card title="Variants">
                      <Card.Section title="Default">
                        <FormLayout>
                          <FormState.Nested field={firstVariant}>
                            {({option, value, price}) => (
                              <React.Fragment>
                                <TextField label="option" {...option} />
                                <TextField label="value" {...value} />
                                <TextField label="price" {...price} />
                              </React.Fragment>
                            )}
                          </FormState.Nested>
                        </FormLayout>
                      </Card.Section>
  
                      <Card.Section>
                        <FormLayout>
                          <FormState.List field={variants}>
                            {({option, value, price}) => (
                              <React.Fragment>
                                <TextField label="option" {...option} />
                                <TextField label="value" {...value} />
                                <TextField label="price" {...price} />
                              </React.Fragment>
                            )}
                          </FormState.List>
                          <ButtonGroup>
                            <Button onClick={addVariant} primary>
                              Add variant
                            </Button>
                          </ButtonGroup>
                        </FormLayout>
                      </Card.Section>
                    </Card>
                  </Layout.Section>
  
                  <Layout.Section>
                    <PageActions
                      primaryAction={submitAction}
                      secondaryActions={[resetAction]}
                    />
                  </Layout.Section>
                </Layout>
              </Page>
            </Form>
          );
        }}
      </FormState>
    );
  }
  ```
  