React Forms That Write Themselves
Shopify's engineering team builds a lot of React forms, and the open source Quilt repo holds the tools they use to do it. The repo contains 72 npm packages, including react-form, which aims to manage React forms "tersely and safely-typed with no effort using React hooks." This walkthrough covers the core hooks and shows how to wire up a basic form.
Working With useForm and useField
The react-form documentation splits its API into three sections: Hooks, Validation, and Utilities. Of the eight available hooks, the two you'll reach for most often areuseForm and useField.
useForm is a custom hook that manages the state of an entire form. It relies on many of the other hooks internally and returns an object containing all the fields needed to run the form. When paired with useField, which handles the state and validations of an individual input, you get sensible defaults for common form patterns without writing much boilerplate.
Stripping Out Old State Logic
BecauseuseForm takes over form state management, you no longer need local useState calls or the handler functions that updated input values. The only thing you keep is the submit handler, which gets replaced by the one returned from useForm.
After removing the previous state and handler logic from the component, React starts throwing ReferenceErrors for missing functions like handleTitleChange. This happens because the TextField components still reference those handlers. Temporarily remove the onChange events and the value prop from both components—they'll come back via the fields object provided by useForm.
The Page component's onAction property also references the now-deleted handleSubmit. The useForm hook provides its own submit function that does the same job, so assign that to onAction for now. Finally, remove the useState import, since form state no longer lives in the component directly.
Bringing In the Hooks
With the old code cleaned out, import both hooks fromreact-form (which is already installed as a dependency in this setup):
import { useForm, useField } from "@shopify/react-form”;
Looking at the first useForm example in the documentation, you can see the hook accepts a configuration object that defines the form fields and an onSubmit function. The fields are each assigned their own useField() invocation.
Without the value and onChange props, the text inputs no longer capture or display text. Those props now live inside the fields object. Adding a console log to inspect the object structure shows that each key (like description) contains everything needed, including onChange and value. Spread them onto each TextField component by destructuring the matching key from fields.
The field object also exposes properties like reset and dirty, which come in handy once the submit function is connected.
Handling Submissions and Extra State
Now it's time to actually submit the form. Remove the quotes aroundsubmit assigned to the Page component's onAction prop. To verify the onSubmit function fires correctly, add a console log to inspect the fields object, then enter a title and description and hit Save.
useForm provides two more features worth using: reset and dirty.
Clearing the Form After Save
Thereset method clears the form and gives the user a blank slate for entering another item. Call it only after the field data has been sent to the backend and handled, but before the return statement. With that in place, submitting text clears the inputs as expected.
Disabling Save Until There's Something to Save
Thedirty flag indicates whether any field has been changed. Use it to disable the Save button until the user types into one of the inputs. The Page component has a disabled property on its Save button; assign it the value !dirty. Since dirty starts as false, the inverted value enables the button only after an edit occurs.
Requiring Input Before Submit
Enabling the Save button as soon as the user types into either field exposes a problem: a product can be submitted without a title. The form should block submission until the Title field has some content, with an error message clarifying what’s missing.
To enforce this, pull the notEmpty hook from react-form. Adding it changes how useField is called—it now takes a configuration object with two keys:
value: tracks the field’s current input valuevalidates: runs validation logic against that value
Passing notEmpty into validates for the title field means the form rejects an empty title on submit and surfaces a required-field error to the user.
Testing the workflow—entering only a description, then clicking Save—confirms the validation fires and the form stays in a valid, expected state.
Where to Go From Here
The example pulls together the core behavior enabled by useForm and useField: stateful fields, dirty tracking, and declarative validation. Those two hooks carry considerably more capability beyond what this walkthrough covers, particularly when forms grow in complexity.
react-form is part of Shopify’s open source Quilt repository, which also includes several other React-focused hooks worth exploring:
react-graphql: type-safe, asynchronous GraphQL components for Reactreact-testing: utilities for testing React components against Shopify’s conventionsreact-i18n: internationalization helpers for translations and formatting
For a practical starting point, the hook-based API keeps form code close to the component logic it drives, making state and validation behavior explicit rather than scattered through the tree.



