What's Changed
Breaking Changes
Conform has undergone a massive redesign in its validation mechanism. This includes replacing some of the high level abstractions with a new set of APIs. Revisitng the updated docs are strongly recommended. Changes include:
- The
useForm
hook is updated with new config and return type:
// Before
import { useForm } from '@conform-to/react';
function ExampleForm() {
const formProps = useForm({
// ...
});
return <form {...formProps}>{/* ... */}</form>
}
// Now
import { useForm } from '@conform-to/react';
function ExampleForm() {
// If you are using remix:
const state = useActionData();
/**
* The hook now returns a `form` object with
* - `form.props` equivalent to the previous `formProps`
* - `form.ref` which is just a shortcut of `form.props.ref`
* - `form.config` which wraps `defaultValue` and `initialError`
* bases on the new `defaultValue` and `state` config
* - `form.error` which represent the form-level error
*/
const form = useForm({
/**
* New validation mode config, default to `client-only`
* Please check the new validation guide for details
*/
mode: 'client-only',
/**
* Default value of the form. Used to serve the `form.config`.
*/
defaultValue: undefined,
/**
* Last submission state. Used to serve the `form.config`
*/
state,
/**
* The `validate` config is renamed to `onValidate`
*/
onValidate({ form, formData }) {
// ...
},
// ... the rest of the config remains the same
})
const fieldset = useFieldset(form.ref, form.config);
return <form {...form.props}>{/* ... */}</form>
}
- The
resolve(schema).parse
API on both schema resolver is now replaced by parse with manual validation.
// Before
import { resolve } from '@conform-to/zod';
import { z } from 'zod';
const schema = resolve(
z.object({
// ...
}),
);
export let action = async ({ request }: ActionArgs) => {
const formData = await request.formData();
const submission = schema.parse(formData);
if (submission.state !== 'accepted') {
return submission.form;
}
return await process(submission.data);
};
// Now
import { formatError } from '@conform-to/zod';
import { parse } from '@conform-to/react';
import { z } from 'zod';
// Raw zod schema
const schema = z.object({
// ...
});
export let action = async ({ request }: ActionArgs) => {
const formData = await request.formData();
/**
* The `submission` object is slightly different
* in the new version, with additional information
* like `submission.type` and `submission.intent`
*
* Learn more about it here: https://conform.guide/submission
*/
const submission = parse(formData);
try {
switch (submission.type) {
case 'valdiate':
case 'submit': {
// schema.parse() is a Zod API
const data = schema.parse(submissio.value);
// Skip if the submission is meant for validation only
if (submission.type === 'submit') {
return await process(data);
}
break;
}
}
} catch (error) {
// formatError is a new API provided by the schema resolver that
// transform the zod error to the conform error structure
submission.error.push(...formatError(error));
}
// Always returns the submission state until the submission is `done`
return submission;
};
- The
resolve(schema).validate
API is also replaced byvalidate()
:
// Before
import { resolve } from '@conform-to/zod';
import { z } from 'zod';
const schema = resolve(
z.object({
// ...
}),
);
export default function ExampleForm() {
const form = useForm({
validate: schema.validate,
});
// ...
}
// Now
import { validate } from '@conform-to/zod';
import { z } from 'zod';
// Raw zod schema
const schema = z.object({
// ...
});
export default function ExampleForm() {
const form = useForm({
// The `validate` config is renamed to `onValidate`
onValidate({ formData }) {
return validate(formData, schema);
},
});
// ...
}
/**
* The new `valdiate` API is just a wrapper on top of
* `parse` and `formatError`, so you can also do this:
*/
export default function ExampleForm() {
const form = useForm({
onValidate({ formData }) {
const submission = parse(formData);
try {
schema.parse(submission.value);
} catch (error) {
submission.error.push(...formatError(error));
}
return submission;
},
});
// ...
}
- The parsed value (i.e.
submission.value
) no longer removes empty string, which will affect how zod handlesrequired
error
/**
* Before v0.4, empty field value are removed from the form data before passing to the schema
* This allows empty string being treated as `undefiend` by zod to utilise `required_error`
* e.g. `z.string({ required_error: 'Required' })`
*
* However, this introduced an unexpected behaviour which stop the schema from running
* `.refine()` calls until all the defined fields are filled with at least 1 characters
*
* In short, please use `z.string().min(1, 'Required')` instead of `z.string({ required_error: 'Required' })` now
*/
const schema = z.object({
name: z.string().min(1, 'Name is required'),
email: z.string().min(1, 'Email is required').email('Email is invalid'),
title: z.string().min(1, 'Title is required').max(20, 'Title is too long'),
});
Improvements
- Conform is now able to autofocus first error field for both client validation and server validation
Docs
- Add comment about button values by @brandonpittman in #36
Special thanks to @brandonpittman for the kind words and support!
Full Changelog: v0.3.1...v0.4.0