zod-prisma-types
zod-prisma-types copied to clipboard
Date values come out as string | Date | null | undefined rather than expected Date | null | undefined
I am attempting to use the generated TaskUncheckedCreateInputSchema schema in order to validate a type on my frontend. However, the value that comes out of this is potentially leaving my Dates (the start and end fields) as strings.
Relevant Frontend Bits (React + Mantine):
export const TaskMutationView = ({
task,
parent,
project,
epic,
onSuccess,
}: {
task?: Task;
parent?: Task;
project?: Project;
epic?: Epic;
onSuccess: () => void;
}) => {
const initialValues: z.infer<typeof TaskUncheckedCreateInputSchema> = {
title: "",
description: "",
projectId: project?.id,
epicId: epic?.id,
parentId: parent?.id,
};
const form = useForm({
initialValues: task ?? initialValues,
});
return (
<SimpleGrid cols={2} breakpoints={[{ maxWidth: 600, cols: 1 }]}>
<DatePickerInput
clearable
value={form.values.start}
onChange={(v) => form.setFieldValue("start", v)}
label={"Start Date"}
minDate={new Date()}
popoverProps={{ withinPortal: true }}
onKeyDown={getHotkeyHandler([["enter", onSubmit]])}
/>
)
}
The value={form.values.start} is giving me the following TypeScript error:
TS2322: Type 'string | Date | null | undefined' is not assignable to type 'DateValue | undefined'. Type 'string' is not assignable to type 'Date'. PickerBaseProps.d.ts(6, 5): The expected type comes from property 'value' which is declared here on type 'IntrinsicAttributes & DatePickerInputProps<"default">'
My suspicion is that this has to do with the .coerce() that zod parses dates through, but I'm not quite sure what the recommended approach to dealing with this is outside of type assertions and other escape hatches.
Prisma Model
model Task {
// Important Metadata
id String @id @default(auto()) @map("_id") @db.ObjectId
completed Boolean @default(false)
title String /// @zod.string.min(1)
size TaskSize @default(MEDIUM)
description String
number Int /// @zod.custom.omit(["input"])
start DateTime? // Date at which this task can be worked on
end DateTime? // Day this task is due
url String? // Page that triggered this task to be created
// Subtasks
parentId String? @db.ObjectId
parent Task? @relation(name: "TaskToTask-Subtasks", fields: [parentId], references: [id], onDelete: NoAction, onUpdate: NoAction)
subtasks Task[] @relation(name: "TaskToTask-Subtasks")
// Dependents & Dependencies
dependencies Task[] @relation("TaskToTask-Dependencies", fields: [dependenciesIds], references: [id])
dependenciesIds String[] @db.ObjectId
dependents Task[] @relation("TaskToTask-Dependencies", fields: [dependentsIds], references: [id])
dependentsIds String[] @db.ObjectId
// Location
location Location? @relation(fields: [locationId], references: [id])
locationId String? @db.ObjectId
// Parent epic
Epic Epic? @relation(fields: [epicId], references: [id])
epicId String? @unique @db.ObjectId
// Parent project
Project Project? @relation(fields: [projectId], references: [id])
projectId String? @unique @db.ObjectId
// Ingestion event that triggered this task to be created, if any
ingestionEvent IngestionEvent? @relation(fields: [ingestionEventId], references: [id])
ingestionEventId String? @unique @db.ObjectId
User User @relation(fields: [userEmail], references: [email])
userEmail String @unique /// @zod.custom.omit(["input"])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
EDIT: Adding the following parses before my render code fixes things. Doesn't feel like a solid fix for me, as now it's just a fatal error rather than undefined behavior, but hopefully helps diagnose what's going on.
form.values.start = z.date().optional().parse(form.values.start);
form.values.end = z.date().optional().parse(form.values.end);
Love the work you've done - so much that I actually started sponsoring you on GitHub! This project saves me a bunch of boilerplate when it comes to tRPC handlers for simple (and even slightly complicated) CRUD handlers.
Probably a bit late, but we had the same problem. We solved it by using superjson as transformer in trpc:
import { initTRPC } from '@trpc/server';
import superjson from 'superjson';
import type { Context } from './context';
const t = initTRPC.context<Context>().create({ transformer: superjson });
// ...