[Simple Generics] Enhanced Generic Type for zod in TypeScript child components

My form structure involves a base interface which is extended by each page to create a more detailed form. Now, I am looking to develop a child component that can generate a form using the extended template.

view minimal reproducible example

// childComponent.tsx  
export default function ProductSourceFormTemplate<T extends IProductSourceBase>({ formSchema }: {
    formSchema: z.ZodType<T>,
}) {  
    type FormValues = z.infer<typeof formSchema>
    const defaultValues: T = useMemo<T>(() => ({
        sourceId: ""
    }) as T, [searchParams])
    const form = useForm<FormValues>({
        resolver: zodResolver(formSchema),
        defaultValues: defaultValues,
    });

    // display form
}

However, an error occurs with defaultValues:

Type 'T' is not assignable to type 'AsyncDefaultValues<T> | DefaultValues<T> | undefined'.
  Type 'IProductSourceBase' is not assignable to type 'AsyncDefaultValues<T> | DefaultValues<T> | undefined'.
    Type 'IProductSourceBase' is not assignable to type 'DefaultValues<T>'.ts(2322)

In addition, none of the names are recognized when attempting to create form elements.

What I aim for is the parent component to create productBaseSchema.extend({}), which is a z.ZodObject<{}>, and then pass this extended schema to the child as

const extendedSchema: z.ZodObject<z.objectUtil.extendShape<{}>
. The child should also provide onSubmit and render functions in its props so the parent only deals with additional fields.

For example:


export interface IProductSourceBase {
    productId: string;
    sourceId: number;
    sourceMeta?: any;
}

const productSourceBaseSchema = z.object({
    productId: z.string().nonempty(translate("Product ID is required")),
    sourceId: z.coerce.number().int(translate("Source ID must be an integer")).gt(0, translate("A source must be selected")),
});

const extendedSchema = productSourceBaseSchema.extend({
    sourceMeta: z.object({
        sourceUrl: z.string().url(translate("This is not a valid URL")).nonempty(translate("Url is required")).refine(
            (url) => /\/spreadsheets\/d\/([a-zA-Z0-9-_]{16,44})/.test(url), // Test for valid Spreadsheet ID
            {
                message: translate("This is not a valid Google Spreadsheet ID"), // Custom error message
            }
        ),
        cell: z.string().nonempty(translate("Cell number is required")).refine(
            (cell) => /^[A-Za-z]+[1-9]\d*$/.test(cell), // Check if the cell matches the pattern
            {
                message: translate("Invalid cell reference format"), // Custom error message for invalid format
            }
        ),
    }),
});

Answer №1

Make sure to set FormValues as the value for both defaultValues and useForm, using DefaultValues from react-hook-form to apply a deep partial to the specified type for clarity. When dealing with forms that involve a transform or default in your zod schema, consider utilizing z.input over z.infer for smoother implementation, although it may not be necessary in this scenario.

import { DefaultValues, ... } from "react-hook-form"

...
    type FormValues = z.infer<typeof formSchema>
    const defaultValues = useMemo<DefaultValues<FormValues>>(() => ({
      sourceId: ""
    }), [])
    
    const form = useForm<FormValues>({
      resolver: zodResolver(formSchema),
      defaultValues,
    });
...

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Difficulty locating the module in Typescript/Javascript

Currently facing an issue while trying to incorporate a module called "request" into my Angular 2 Typescript project from here. Despite following the usual installation process with npm install --save request and also attempting typings install request -- ...

Perform validation for a form field in Angular 2 asynchronously by making an HTTP request

I have a concept where the user can submit a form and if the email is already registered, an error triggered by the API should display. I am using reactive forms with FormBuilder and trying to implement the validator in the subscribe error handler. Constr ...

When `strictNullChecks` is turned on, how does the `void` type differ from the `undefined` literal type?

When strictNullChecks is turned on: (u: undefined, v: void, n: null) => { v = u; u = v; // type error: Type 'void' is not assignable to type 'undefined' v = n; // type error: Type 'null' is not assignable to type &ap ...

Setting an attribute on a custom component that is dynamically created within an Angular application

I am working with a custom library component called <my-icon>. To display the icon, I need to specify the property [name] like this: <my-icon [name]='warning'></my-icon> Currently, I am dynamically creating these icons in Type ...

Remove the ability to select from the dropped list item

Here is the HTML and Javascript code I used to enable drag and drop functionality for list items from one div to another: HTML: <div class="listArea"> <h4> Drag and Drop list in Green Area: </h4> <ul class="unstyle"> & ...

How to make an optional prop with a default value non-nullable in a ts+react component?

Is there a way to modify a React component to accept an optional prop and then treat it as non-null within the component itself? For example, consider the following basic component: import React from 'react'; type props = { x?: number; }; c ...

Attempting to assign incompatible types in TypeScript

I'm encountering an error that I can't quite figure out: Type '"New Choice"' is not assignable to type '"Yes" | "No"'.ts(2322) test.ts(17, 14): The expected type comes from property 'text&apo ...

Ionic user interface is not appearing on screen

While following a tutorial on this website, I encountered my first issue in the file todo.service.ts: An error stating "Property 'key' does not exist on type 'Todo'" was displayed. Below is the interface code for todo.ts: export inte ...

Error: Unable to access undefined constructor or null value when executing next.js

While executing the command npm run dev An unexpected error occurred in my project code: TypeError: Class extends value undefined is not a constructor or null at Object.618 (/Users/EugeneBos/Websites/dotalaning/node_modules/next/dist/compiled/postcss- ...

Upon completion of the function, the ForEach loop commences

I'm encountering an issue with my code. I am trying to verify if there is an item in the cutlist array that has a material_id which does not exist in the materials database. However, the code within the forEach loop is being executed after the functio ...

Using Next.js in conjunction with Prisma to perform an upsert operation based on two specific conditions

My current challenge involves using Prisma to achieve the following: I need to update the "name" field of a category row if one exists with the same hash and user_id, otherwise I need to create the row. However, TypeScript is throwing an error stating th ...

Hover shows no response

I'm having trouble with my hover effect. I want an element to only be visible when hovered over, but it's not working as expected. I've considered replacing the i tag with an a, and have also tried using both display: none and display: bloc ...

How to change a specific value in an array of objects using React

Within my array, I have objects containing the fields id and title const cols = [ { id: 0, title: "TODO" }, { id: 1, title: "InProgress" }, { id: 2, title: "Testing" }, { ...

Utilizing Next.js for dynamic routing with [[...slug.js]] allows for comprehensive URL handling and seamless 404 page display for links leading back to the homepage - a feature

In order to achieve a single dynamic route for handling all requests in this application, I have created a file called [[...slug]].js. Data loading is managed using getServerSideProps(), allowing for server-side rendering. Notably, there are no index.js fi ...

Tips for accessing a specific ListItem within the Menu Component using MUI for React

Within my code, I am working with a List that contains multiple ListItems pulled from an array called myCollection. Each ListItem has a MenuIcon element which triggers a menu to appear, providing the option to delete the specific item. Here is a simplified ...

Utilizing React to Combine Responses from Multiple APIs with Unique Error Handling Methods

Is it possible to implement different error handling for React Promise with multiple APIs? Can the APIs be executed simultaneously to optimize time efficiency? Is it achievable to have unique error handling procedures for each API independently? If one AP ...

Issue with remounting in Nextjs 13

import { useRouter, useSearchParams, usePathname } from 'next/navigation'; export function useQueryParams() { const pathname = usePathname(); const router = useRouter(); const searchParams = useSearchParams()!; const updateQu ...

Using Array.map and filter together can result in multiple fetch requests being made

Currently, I am in the process of developing an application using Next.js 13 in conjunction with App Routes. However, I have encountered a minor obstacle and could use some assistance. In one of the pages, I have arranged a grid to display information, al ...

"Exciting developments in Angular 17 with the introduction of the new @

I need to output elements from an array of strings starting at index 1. arr = [ "str1", "str2", "str3", "str4", "str5" ] The desired output is: str2 str3 str4 str5 To achieve this, use a new @for loop in ...

utilizing type predictors in brand merging

For hours now, I've been struggling with a small problem that seems to have no solution in sight. I wonder if someone with a sharper mind could offer me some guidance? The method I'm using returns a predicate: this is xxx. This method is then us ...