edmundhung / conform

A type-safe form validation library utilizing web fundamentals to progressively enhance HTML Forms with full support for server frameworks like Remix and Next.js.
https://conform.guide
MIT License
1.8k stars 101 forks source link

Client-side validation with discriminated union type always results in an unhelpful error #731

Closed Varkoff closed 1 month ago

Varkoff commented 1 month ago

Describe the bug and the expected behavior

When using client-side form validation with Conform, Zod and Remix with a Discriminated Union Type, I get an unclear error : "Invalid input".

Demo in this stackblitz

Here is a simple repro :

import { Form } from '@remix-run/react';
import { z } from 'zod';
import { useForm, getFormProps, getInputProps } from '@conform-to/react';
import { getZodConstraint, parseWithZod } from '@conform-to/zod';
const SimpleSchema = z.object({
  name: z.string({
    required_error: 'Name is required',
  }),
  type: z.literal('name'),
});
const AgeSchema = SimpleSchema.extend({
  type: z.literal('age'),
  age: z.number({
    required_error: 'Age is required',
  }),
});

const UnionSchema = z.union([SimpleSchema, AgeSchema]);
export default function Index() {
  const [form, fields] = useForm({
    constraint: getZodConstraint(UnionSchema),
    onValidate({ formData }) {
      const parsedValue = parseWithZod(formData, { schema: UnionSchema });
      console.log(parsedValue);
      return parsedValue;
    },
  });
  return (
      <Form
        method="POST"
        style={{
          flexDirection: 'column',
        }}
        {...getFormProps(form)}
      >
        <label htmlFor="name"> Name </label>
        <input
          style={{
            border: '1px solid black',
          }}
          {...getInputProps(fields.name, {
            type: 'text',
          })}
        />
        <ErrorComponent errors={fields.age.errors} />
        <ErrorComponent errors={fields.name.errors} />
        <ErrorComponent errors={form.errors} />
        <button style={{ background: 'blue', color: 'white' }} type="submit">
          Submit
        </button>
      </Form>
  );
}

const ErrorComponent = ({ errors }: { errors: string[] | undefined }) => {
  if (!errors) return null;
  return <div style={{ color: 'red' }}>{`${errors}`}</div>;
};

Here is an example object output of the failed validation :

{
    "status": "error",
    "payload": {
        "name": "",
        "age": "",
        "type": "age"
    },
    "error": {
        "": [
            "Invalid input"
        ]
    }
}

We do not know "which" property triggered the error, which property is missing and overriding the error message to better identify the source does not seem to be working.

This is specific to client-side validation, as server-side validation works.

Expected behavior :

Conform version

v1.1.5

Steps to Reproduce the Bug or Issue

  1. Submit the form, either empty or with values in it.

What browsers are you seeing the problem on?

Chrome

Screenshots or Videos

No response

Additional context

No response

edmundhung commented 1 month ago

Zod's union implementation is not smart enough to differentiate two schemas automatically at the moment. What you are looking for is z.discriminatedUnion('type', [SimpleSchema, AgeSchema])