If you want to validate an array of objects from this Schema
const fieldsOfEng = [
{
id: "ELECTRICAL",
name: "Electrical",
experience: 1,
},
{
id: "MECHANICAL",
name: "Mechanical",
experience: undefined,
},
];
I'd do it like this
const userInfoSchema = z.object({
id: z.string(),
name: z.string(),
experience: z.number().optional()
})
// Now add this object into an array
const usersInfoSchema = z.array(userInfoSchema)
Answer from Ark on Stack OverflowIf you want to validate an array of objects from this Schema
const fieldsOfEng = [
{
id: "ELECTRICAL",
name: "Electrical",
experience: 1,
},
{
id: "MECHANICAL",
name: "Mechanical",
experience: undefined,
},
];
I'd do it like this
const userInfoSchema = z.object({
id: z.string(),
name: z.string(),
experience: z.number().optional()
})
// Now add this object into an array
const usersInfoSchema = z.array(userInfoSchema)
This answer is an update on my progress so far. I closer to getting my schema validation working correctly but for some reason it is not submitting. I could solve this faster if I could get react-hook form's errors to output correctly but since I'm using template literals when I'm registering the inputs, the error is just outputting nothing. I DO know there is an error though because my truthy error outputs the "professions:" string that I added in the p-tag.
Data for checkboxes in form (this is the data I'm validating via zod):
const fieldsOfEng = [
{
id: "ELECTRICAL",
name: "Electrical",
experience: undefined,
},
{
id: "MECHANICAL",
name: "Mechanical",
experience: undefined,
},
Schema for validation: I'm checking that the name they select is included in my fieldsOfEng array and making sure the experience is greater than 1.
const userInfoSchema = object({
professions: z
.object({
name: z
.string()
.refine((name) =>
fieldsOfEng.map((field) => field.name).includes(name)
),
experience: z.number().refine((experience) => experience > 1),
})
.array(),
});
React hook form:
type userInfoType = z.infer<typeof userInfoSchema>;
const {
register,
watch,
handleSubmit,
formState: { errors, isSubmitting },
} = useForm<userInfoType>({
resolver: zodResolver(userInfoSchema),
});
Form: Notice I am using template literals for registering each input. It took me forever to figure this out. So far this is the only way I've been able to get the data to output correctly (via react-hook-form's 'watch' method).
<fieldset>
<legend>Practicing fields of engineering</legend>
{fieldsOfEng.map((field, i) => {
return (
<div key={field.id}>
<div>
<input
{...register(`professions.${i}.name`)} <--REGISTERED HERE
value={field.name}
type="checkbox"
/>
<div>
<input
{...register(`professions.${i}.experience`)} <--REGISTERED HERE
type="number"
value={field.experience}
placeholder="Yrs Experience"
/>
</div>
</div>
<div>
{errors.professions && (
<p>
professions:
{errors.professions.message}
</p>
)}
</div>
</div>
);
})}
</fieldset>
how can I validate an array of objects where each item should contain only one property ?
typescript - Is there a way to have an array of objects with some of them literals? - Stack Overflow
Please help - deadline tmrw: How to dynamically add array of objects to React Hook Form (TS + ShadCN + Zod)?
Array of objects with some of them literals
I'm fairly new to React and I'm trying to build a dynamic form that includes an array of route stops using RHF + Typescript + Zod + ShadCN. I've reviewed the docs and have been able to implement simple text and []string fields, but am blocked at the section of a dynamic object array.
I followed the instructions here to add the ShadCN form components. Below is the relevant code. I'm using useFieldArray and it makes perfect sense for string arrays, but I can't get it to work for an object array. I've shown the different things I've tried in the comments of the field. When I try {...form.register(stops.${index}.city)}, it correctly renders the default values, but the submit button doesn't do anything and the validation will throw undefined or false negatives for wrong input. The other fields work just fine. I would greatly appreciate any help on this! Thank you for your time!
import "./styles.css";
import { zodResolver } from "@hookform/resolvers/zod";
import { useFieldArray, useForm } from "react-hook-form";
import * as z from "zod";
import { Button } from "./button";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "./form";
import { Input } from "./input";
const stopObj = z.object({
order: z.coerce.number(),
zip: z.string(),
city: z.string({ required_error: "This field is required." }),
state: z
.string({ required_error: "This field is required." })
.max(2, "2-letter state abbreviation"),
country: z.coerce
.string()
.max(2, "Two-letter country code (ISO 3166-1 alpha-2)"),
});
const formSchema = z.object({
firstName: z.string({ required_error: "This field is required." }),
currency: z.enum(["USD", "CAD"]),
stops: z
.array(stopObj)
.min(2, "2 stops minimum required (pickup and dropoff)"),
});
export default function App() {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
firstName: "John",
currency: "USD",
stops: [
{ order: 1, city: "New York", state: "NY", zip: "02116", country: "US" },
{ order: 2, city: "Austin", state: "TX", zip: "12345", country: "US" },
],
},
});
async function onSubmit(values: z.infer<typeof formSchema>) {
console.log("onSubmit:", values);
}
const { fields } = useFieldArray({ name: "stops", control: form.control });
return (
<div className="bg-[#305645] w-full justify-center flex">
<div className="w-3/4 bg-white px-5 py-3 my-20 rounded-lg">
<Form {...form}>
<div className="text-center font-medium">
<h1 style={{ fontSize: "2rem", marginBottom: "1rem" }}>
Complete Form
</h1>
</div>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<div className="flex space-x-4 w-full">
<div className="w-1/2">
<FormField
control={form.control}
name="firstName"
render={({ field }) => (
<FormItem>
<FormLabel>First Name</FormLabel>
<FormControl>
<Input placeholder="John" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<FormField
control={form.control}
name="currency"
render={({ field }) => (
<FormItem>
<FormLabel>Currency</FormLabel>
<FormControl>
<Input placeholder="USD" {...field} />
</FormControl>
<FormDescription>USD or CAD.</FormDescription>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* FIXME how to correctly render and update each stop object?? */}
{fields.map((field, index) => (
<FormField
control={form.control}
key={field.id}
name={`stops.${index}`}
render={({ field }) => (
<>
<FormItem>
<FormLabel>City</FormLabel>
<FormDescription />
<FormControl>
<Input
{...field.value.city}
// {...form.register(`stops.${index}.city`)}
// defaultValue={field.name}
// value={field.value.city}
// onChange={(e) => field.onChange(e.target.value)}
// onBlur={(e) => field.onBlur(e.target.value)}
/>
</FormControl>
<FormMessage />
</FormItem>
<FormItem>
<FormLabel>State</FormLabel>
<FormDescription />
<FormControl>
<Input
{...form.register(`stops.${index}.state`)}
// defaultValue={field.name}
/>
</FormControl>
<FormMessage />
</FormItem>
</>
)}
/>
))}
<Button
type="submit"
className="bg-orange-500 font-extrabold w-full"
>
Submit
</Button>
</form>
</Form>
</div>
</div>
);
}**Screenshot:**Here's a screenshot. The currency enum field works as expected, but the city & state ones do not. Even the behavior shown in the image inconsistent. It took me a few retries for the issue to appear again so I could screenshot. I'm also not getting console or compiler errors.
Update: Here's the closest thing I've found to what I'm trying to do, but I'm struggling to figure out how to translate this to ShadCN UI.
I have the following TypeScript code:
export const allRelicTypeMainStats = {
[CavernRelicType.Head]: ["HPDelta"],
[CavernRelicType.Hands]: ["AttackDelta"],
[CavernRelicType.Body]: [
"HPAddedRatio",
"AttackAddedRatio",
"DefenceAddedRatio",
"CriticalChanceBase",
"CriticalDamageBase",
"HealRatioBase",
"StatusProbabilityBase",
],
[CavernRelicType.Feet]: [
"HPAddedRatio",
"AttackAddedRatio",
"DefenceAddedRatio",
"SpeedDelta",
],
[PlanarOrnamentType.PlanarSphere]: [
"HPAddedRatio",
"AttackAddedRatio",
"DefenceAddedRatio",
"PhysicalAddedRatio",
"FireAddedRatio",
"IceAddedRatio",
"ThunderAddedRatio",
"WindAddedRatio",
"QuantumAddedRatio",
"ImaginaryAddedRatio",
],
[PlanarOrnamentType.LinkRope]: [
"BreakDamageAddedRatioBase",
"SPRatioBase",
"HPAddedRatio",
"AttackAddedRatio",
"DefenceAddedRatio",
],
} as const;
export type RelicMainStat = (typeof allRelicTypeMainStats)[RelicType][number];
I want to make a zod type that's equivalent of RelicMainStat, with other types that use [...] as const I could use stuff like union, enum, etc. but for this type I can't find a way to convert it into a zod type. Any help? I can't seem to find anything for this. The closest thing I could find is something like nativeEnum, which uses an object as const as well, but mine goes one more level deeper, using T[] as opposed to T.
P.S. PlanarOrnamentType and CavernRelicType are TypeScript enums (dw both don't have conflicting values), and are union'ed to a type named RelicType.
EDIT 1:
I've tried the following, and it seems to work, however idk if it's the best way for something like this:
const ZodRelicMainStat = z.enum([ ...allRelicTypeMainStats[CavernRelicType.Head], ...allRelicTypeMainStats[CavernRelicType.Hands], ...allRelicTypeMainStats[CavernRelicType.Body], ...allRelicTypeMainStats[CavernRelicType.Feet], ...allRelicTypeMainStats[PlanarOrnamentType.PlanarSphere], ...allRelicTypeMainStats[PlanarOrnamentType.LinkRope] ] as const)