npm @conform-to/react 0.7.0
v0.7.0

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

Breaking Changes

  • Improved ESM compatibility with Node and Typescript. If you were running on ESM with hydration issue like this report, please upgrade. (#159, #160)
  • The initialReport config is now removed. Please use the shouldValidate and shouldRevalidate config instead (#176)
  • The shouldRevalidate config will now default to the shouldValidate config instead of onInput (#184)
  • The useInputEvent hook requires a ref object now (#173)
// Before - always returns a tuple with both ref and control object
const [ref, control] = useInputEvent();

// After - You need to provide a ref object now
const ref = useRef<HTMLInputElement>(null);
const control = useInputEvent({
  ref,
});

// Or you can provide a function as ref 
const control = useInputEvent({
  ref: () => document.getElementById('do whatever you want'),
});
  • The conform helpers no longer derive aria attributes by default. You can enable it with the ariaAttributes option (#183)
// Before
function Example() {
    const [form, { message }] = useForm();

    return (
        <form>
            <input {...conform.input(message, { type: 'text' })} />
        </form>
    )
}

// After
function Example() {
    const [form, { message }] = useForm();

    return (
        <form>
            <input
                {...conform.input(message, {
                    type: 'text',
                    ariaAttributes: true, // default to `false`
                })}
            />
        </form>
    )
}

Improvements

  • Conform will now track when the lastSubmission is cleared and triggered a form reset automatically. (Note: The example below is updated with the new approach introduced on v0.7.2 instead)
export let action = async ({ request }: ActionArgs) => {
    const formData = await request.formData();
    const submission = parse(formData, { schema });

    if (submission.intent !== 'submit' || !submission.value) {
        return json(submission);
    }

    return json({
        ...submission,
        // Notify the client to reset the form using `null`
        payload: null,
    });
};

export default function Component() {
    const lastSubmission = useActionData<typeof action>();
    const [form, { message }] = useForm({
        // The last submission should be updated regardless the submission is successful or not
        // If the submission payload is empty:
        // 1. the form will be reset automatically
        // 2. the default value of the form will also be reset if the document is reloaded (e.g. nojs)
        lastSubmission,
    })

    // ...
}
Original approach on v0.7.0
const actionData = useActionData();
const [form, fields] = useForm({
  // Pass the submission only if the action was failed
  // Or, skip sending the submission back on success
  lastSubmission: !actionData?.success ? actionData?.submission : null,
});
  • New refine helper to reduce the boilerplate when setting up async validation with zod (#167)
// Before
function createSchema(
  intent: string,
  constraints: {
    isEmailUnique?: (email) => Promise<boolean>;
  } = {},
) {
  return z.object({
    email: z
      .string()
      .min(1, 'Email is required')
      .email('Email is invalid')
      .superRefine((email, ctx) => {
        if (intent !== 'submit' && intent !== 'validate/email') {
          // Validate only when necessary
          ctx.addIssue({
            code: z.ZodIssueCode.custom,
            message: conform.VALIDATION_SKIPPED,
          });
	} else if (typeof constraints.isEmailUnique === 'undefined') {
          // Validate only if the constraint is defined
          ctx.addIssue({
            code: z.ZodIssueCode.custom,
            message: conform.VALIDATION_UNDEFINED,
          });
        } else {
          // Tell zod this is an async validation by returning the promise
          return constraints.isEmailUnique(value).then((isUnique) => {
            if (isUnique) {
              return;
            }
            ctx.addIssue({
              code: z.ZodIssueCode.custom,
              message: 'Email is already used',
            });
          });
        }
      }),
    // ...
  });
}

// After
import { refine } from '@conform-to/zod';

function createSchema(
  intent: string,
  constraints: {
    isEmailUnique?: (email) => Promise<boolean>;
  } = {},
) {
  return z.object({
    email: z
      .string()
      .min(1, 'Email is required')
      .email('Email is invalid')
      .superRefine((email, ctx) =>
        refine(ctx, {
          validate: () => constraints.isEmailUnique?.(email),
          when: intent === 'submit' || intent === 'validate/email',
          message: 'Email is already used',
        }),
      ),
    // ...
  });
}
  • Added basic zod union / discriminatedUnion support when inferring constraint (#165)
const schema = z
    .discriminatedUnion('type', [
        z.object({ type: z.literal('a'), foo: z.string(), baz: z.string() }),
        z.object({ type: z.literal('b'), bar: z.string(), baz: z.string() }),
    ])
    .and(
        z.object({
            qux: z.string(),
        }),
    ),

// Both `foo` and `bar` is considered optional now
// But `baz` and `qux` remains required  
expect(getFieldsetConstraint(schema)).toEqual({
    type: { required: true },
    foo: { required: false },
    bar: { required: false },
    baz: { required: true },
    quz: { required: true },
});

New Contributors

Full Changelog: v0.6.3...v0.7.0

Don't miss a new react release

NewReleases is sending notifications on new releases.