1

I am making a react application using vite, react hook form (7.54.1) and zod (3.24.1) whose main function is a multi step form. Now in my form schemas, I need to customize the default zod messages using refine()/superrefine() but these methods just aren't working even when I provide guaranteed true condition to trigger them.

hardcoded sample data

const data = {
    "Expense": [
      {
        "accountingFieldCode": {
          "fieldCode": "DETAIL_CURRENCY_NAME",
          "fieldCodeName": "Currency Name",
          "description": null
        },
        "accountingOutputName": "sdfds",
        "defaultValueApplicable": "N",
        "defaultValue": "",
        "prefixValue": "",
        "suffixValue": "",
        "fieldCodeConcatRequired": "N",
        "concatCharacter": "",
        "fieldCodeToConcat": [
          {
            "fieldCode": "VENDOR_NAME",
            "fieldCodeName": "Vendor Name",
            "description": null
          },
          {
            "fieldCode": "EMPLOYEE_GL_CODE",
            "fieldCodeName": "Employee GL Code",
            "description": null
          }
        ],
        "maxDataLength": 3
      }
    ]
  };

schema

const defineFieldLineSchema = z.object({
  accountingFieldCode: z
    .object({
      fieldCode: z.string(),
      fieldCodeName: z.string(),
      description: z.string().nullable(),
    }),
  accountingOutputName: z.string().min(1, "Field is required"),
  defaultValueApplicable: z.string(),
  defaultValue: z.string().optional(),
  prefixValue: z.string().optional(),
  suffixValue: z.string().optional(),
  fieldCodeConcatRequired: z.enum(["Y", "N"]),
  concatCharacter: z.string(),
  fieldCodeToConcat: z.object({
    fieldCode: z.string(),
    fieldCodeName: z.string(),
    description: z.string().nullable(),
  }).optional(),
  maxDataLength: z.number(),
}).superRefine((data, ctx) => {
  if (data.fieldCodeConcatRequired === "Y" && !data.fieldCodeToConcat) {
    ctx.addIssue({
      code: z.ZodIssueCode.custom,
      message: "fieldCodeToConcat is required when fieldCodeConcatRequired is 'Y'",
      path: ["fieldCodeToConcat"],
    });
  }
  if (Array.isArray(data.fieldCodeToConcat)) {
    ctx.addIssue({
      code: z.ZodIssueCode.custom,
      message: "fieldCodeToConcat must be an object, not an array",
      path: ["fieldCodeToConcat"],
    });
  }
})

The only error I keep getting is the default zod message "Expected object, received array" instead of "fieldCodeToConcat must be an object, not an array" or "fieldCodeToConcat is required when fieldCodeConcatRequired is 'Y'".

Any help or suggestion regarding this would be greatly appreciated.

2 Answers 2

0

Issue is that superrefine() and refine() is checked only if the initial check is fulfilled. Meaning fieldCodeToConcat has to be an object first according to the condition

fieldCodeToConcat: z.object({
    fieldCode: z.string(),
    fieldCodeName: z.string(),
    description: z.string().nullable(),
  }).optional()

for fine grained validation, fieldCodeToConcat can be changed to a value that is expected to be true so that it goes on to refine() / superrefine() like below. The console log would get printed in this case.

const defineFieldLineSchema = z.object({
  fieldCodeToConcat: z.union([
    z.record(z.any()), // Accepts any object
    z.array(z.record(z.any())) // Accepts an array of any objects
  ]),
  
})
.superRefine((data, ctx) => {
  console.log("HELLO")
  if (Array.isArray(data.fieldCodeToConcat)) {
    ctx.addIssue({
      code: z.ZodIssueCode.custom,
      message: "fieldCodeToConcat must be an object, not an array",
      path: ["fieldCodeToConcat"],
    });
  }
})
Sign up to request clarification or add additional context in comments.

Comments

-1

On my side this is what i faced and how i solved it

Problem

My superRefine was never being reached because of a z.enum rule.

I had default values like this:

const preparedInitialData = {
  editing: false,
  name: "",
  email: "",
  password: "",
  gender: "", // <-- this breaks Zod enum
};

z.enum expects the value to match one of its defined options, null, or undefined. Passing "" caused Zod to fail before superRefine ran.


Problematic schema

const userFormSchema = z.object({
  name: z.string().trim().min(3),
  email: z.string().trim().email(),
  password: z.string().trim().optional(),
  gender: z.enum(["Male", "Female", "Prefer not to say"]), // breaks superRefine if empty
}).superRefine((data, ctx) => {
  console.log("SUPER REFINE RUNNING", data);
});

Solution

Make the enum optional().nullable() and handle the required check manually:

const preparedInitialData = {
  editing: false,
  name: "",
  email: "",
  password: "",
  gender: null, // ✅ fixed to null
};

const userFormSchema = z.object({
  name: z.string().trim().min(3),
  email: z.string().trim().email(),
  password: z.string().trim().optional(),
  gender: z.enum(["Male", "Female", "Prefer not to say"]).optional().nullable(),
}).superRefine((data, ctx) => {
  if (!data.gender) {
    ctx.addIssue({
      code: "custom",
      path: ["gender"],
      message: "Gender is required",
    });
  }

  console.log("SUPER REFINE RUNNING", data);
});

Use with React Hook Form

const {
  control,
  handleSubmit: hookFormHandleSubmit,
  formState: { errors },
  watch,
  setValue,
  reset,
} = useForm({
  resolver: zodResolver(userFormSchema),
  defaultValues: preparedInitialData,
});

✅ Result:

  • superRefine always runs.
  • Conditional validations like password length or role-based checks now work.
  • Default value for gender is correctly null instead of "".

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.