Skip to main content

Migrate Form from Polaris React

Replace Polaris React Form with the native form element. Polaris web-component fields are form-associated, so named fields participate in FormData, submit, and reset behavior.

If the app renders controlled Polaris web-component fields through React, upgrade to React 19 first. React 18 doesn't provide the custom-element property and event behavior this example relies on. If you can't upgrade yet, keep the controlled Polaris React fields during this migration slice.


Anchor to Migrate a settings formMigrate a settings form

The following example submits immediately with a native submit button and shows pending and form-level error states. Add data-save-bar only when the page also needs unsaved-change protection.

Migrating a product settings form

import {useState, type FormEvent} from 'react';

type FormSubmitEvent =
| FormEvent<HTMLFormElement>
| (SubmitEvent & {currentTarget: HTMLFormElement});

export function ProductSettings({initialTitle, saveProduct}) {
const [pending, setPending] = useState(false);
const [submitError, setSubmitError] = useState('');

async function handleSubmit(event: FormSubmitEvent) {
event.preventDefault();
if (pending) return;

setPending(true);
setSubmitError('');

try {
await saveProduct(new FormData(event.currentTarget));
shopify.toast.show('Product saved');
} catch {
setSubmitError("Product couldn't be saved. Check the fields and try again.");
} finally {
setPending(false);
}
}

return (
<form onSubmit={handleSubmit}>
<s-section heading="Product settings">
<s-stack gap="base">
{submitError && (
<s-banner tone="critical" heading="Fix the following error">
{submitError}
</s-banner>
)}
<s-text-field
label="Title"
name="title"
defaultValue={initialTitle}
autocomplete="off"
required
/>
<s-button type="submit" variant="primary" loading={pending}>
Save product
</s-button>
</s-stack>
</s-section>
</form>
);
}
import {Form, TextField} from '@shopify/polaris';

export function ProductSettings({title, setTitle, saveProduct}) {
return (
<Form
noValidate
preventDefault
onSubmit={() => saveProduct({title})}
>
<TextField
label="Title"
name="title"
value={title}
onChange={setTitle}
autoComplete="off"
/>
<button type="submit">Save</button>
</Form>
);
}

Preview


Anchor to Replace Form propertiesReplace Form properties

Polaris ReactNative formMigration notes
onSubmitonSubmitRead named controls with new FormData(event.currentTarget).
preventDefaultevent.preventDefault() inside onSubmitUse it only when app code handles persistence instead of native navigation.
action and methodaction and methodKeep native submission when the server endpoint owns the workflow.
acceptCharset, encType, name, and targetSame native attributesPreserve them only when the endpoint depends on them.
autoComplete booleanautoComplete="on" or autoComplete="off"Prefer field-specific autocomplete tokens when available.
noValidatenoValidateIf set, render actionable field errors from server and client validation.
implicitSubmitNative Enter-key submissionKeep standard behavior unless the old form intentionally disabled it for a documented product rule.
childrenNative and Polaris web-component form controlsGive every submitted control a stable name.

Don't add preventDefault() and then forget to submit. The handler must set pending state, call the backend, render field or form errors, and confirm success.


Anchor to Handle values and validationHandle values and validation

Use uncontrolled fields with defaultValue when the browser and FormData can own the draft. Use controlled value only when the UI must react to every change. Don't mix a controlled value with a second object that becomes the submission source.

Put a single-field validation message on that field's error property. Use s-banner for a submission failure that affects the complete form. After a failed submit, preserve entered values and focus the first invalid field or the form-level error summary.

When the server returns errors, map them by stable field name. Don't rely on translated labels as error keys.


Anchor to Add unsaved-change protection when neededAdd unsaved-change protection when needed

Add data-save-bar to the native form when merchants can leave with unsaved changes. The form's submit event handles Save, and reset handles Discard. Add data-discard-confirmation when discarding is risky.

Use the programmatic Save Bar API instead when dirty state isn't represented by native form controls. Don't use automatic and programmatic save-bar control for the same form.


  • Submit with the primary button and Enter from each eligible field.
  • Verify FormData contains every named value, including web-component fields, choices, and dates.
  • Exercise client and server validation, pending state, duplicate submission, failure, retry, and success.
  • Reset or discard controlled and uncontrolled fields.
  • Navigate away with clean and dirty state when the form uses a save bar.

Anchor to Remove Polaris ReactRemove Polaris React

After every form is migrated, remove Form, callback adapters used only for Polaris value signatures, and duplicate form-state helpers. Remove @shopify/polaris only after no other route in scope imports it.



Was this page helpful?