npm @conform-to/zod 0.4.0
v0.4.0

latest releases: 1.3.0, 1.2.2, 1.2.1...
2 years ago

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 by validate():
// 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 handles required 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

Special thanks to @brandonpittman for the kind words and support!

Full Changelog: v0.3.1...v0.4.0

Don't miss a new zod release

NewReleases is sending notifications on new releases.