From 69e7e2ef5199f550552fb298684731bb4066fef2 Mon Sep 17 00:00:00 2001 From: Isaac Way Date: Sat, 14 Jan 2023 11:05:09 -0600 Subject: [PATCH 1/2] fix typing issues with create unique field schema --- .gitignore | 4 ++- src/__tests__/createSchemaForm.test.tsx | 34 ++++++++++++++++++++++++- src/createFieldSchema.tsx | 15 ++++++----- src/createSchemaForm.tsx | 17 ++++++++----- src/typeUtilities.ts | 30 ++++++++++++++++++++++ 5 files changed, 86 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index 1dc3a23..6a5b8dc 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,6 @@ coverage .yarn .vscode lib -.env \ No newline at end of file +.env + +src/playground.tsx \ No newline at end of file diff --git a/src/__tests__/createSchemaForm.test.tsx b/src/__tests__/createSchemaForm.test.tsx index a0db967..3b87638 100644 --- a/src/__tests__/createSchemaForm.test.tsx +++ b/src/__tests__/createSchemaForm.test.tsx @@ -22,6 +22,7 @@ import { useReqDescription, useTsController, } from "../FieldContext"; +import { createUniqueFieldSchema } from "../createFieldSchema"; const testIds = { textField: "_text-field", @@ -766,5 +767,36 @@ describe("createSchemaForm", () => { render(
{}} />); }).toThrow(); }); - it("should be possible to show nested error messages with the 'errorMessage' helper returned from `useTsController`", () => {}); + it("should correctly type the form with multiple unique field schemas.", () => { + const A = createUniqueFieldSchema(z.string(), "one"); + const B = createUniqueFieldSchema(z.string(), "two"); + function In1(_: { req: string }) { + return
; + } + function In2(_: { req2: string }) { + return
; + } + const mapping = [ + [A, In1], + [B, In2], + ] as const; + + const Form = createTsForm(mapping); + + {}} + props={{ + a: { + req: "One", + }, + b: { + req2: "Two", + }, + }} + />; + }); }); diff --git a/src/createFieldSchema.tsx b/src/createFieldSchema.tsx index e4accf2..f2a201f 100644 --- a/src/createFieldSchema.tsx +++ b/src/createFieldSchema.tsx @@ -1,3 +1,4 @@ +import { z } from "zod"; import { RTFSupportedZodTypes } from "./supportedZodTypes"; export const HIDDEN_ID_PROPERTY = "_rtf_id"; @@ -21,14 +22,14 @@ export function isSchemaWithHiddenProperties( return HIDDEN_ID_PROPERTY in schemaType; } -export function addHiddenProperties( - schema: T, - properties: HiddenProperties -) { +export function addHiddenProperties< + ID extends string, + T extends z.ZodBranded +>(schema: T, properties: HiddenProperties) { for (const key in properties) { (schema as any)[key] = properties[key as keyof typeof properties]; } - return schema; + return schema as T; } export function duplicateIdErrorMessage(id: string) { @@ -59,5 +60,7 @@ export function createUniqueFieldSchema< Identifier extends string >(schema: T, id: Identifier) { const r = schema.brand(); - return addHiddenProperties(r, { [HIDDEN_ID_PROPERTY]: id }); + return addHiddenProperties(r, { + [HIDDEN_ID_PROPERTY]: id, + }) as z.ZodBranded; } diff --git a/src/createSchemaForm.tsx b/src/createSchemaForm.tsx index 8bdcb16..333b624 100644 --- a/src/createSchemaForm.tsx +++ b/src/createSchemaForm.tsx @@ -10,9 +10,14 @@ import { DeepPartial, useForm, UseFormReturn } from "react-hook-form"; import { AnyZodObject, z, ZodEffects } from "zod"; import { getComponentForZodType } from "./getComponentForZodType"; import { zodResolver } from "@hookform/resolvers/zod"; -import { IndexOf, RequireKeysWithRequiredChildren } from "./typeUtilities"; +import { + IndexOf, + IndexOfUnwrapZodType, + RequireKeysWithRequiredChildren, + UnwrapMapping, +} from "./typeUtilities"; import { getMetaInformationForZodType } from "./getMetaInformationForZodType"; -import { unwrapEffects, UnwrapZodType } from "./unwrap"; +import { unwrapEffects } from "./unwrap"; import { RTFBaseZodType, RTFSupportedZodTypes } from "./supportedZodTypes"; import { FieldContextProvider } from "./FieldContext"; import { isZodTypeEqual } from "./isZodTypeEqual"; @@ -292,9 +297,9 @@ export function createTsForm< props?: RequireKeysWithRequiredChildren< Partial<{ [key in keyof z.infer]: Mapping[IndexOf< - Mapping, + UnwrapMapping, readonly [ - UnwrapZodType< + IndexOfUnwrapZodType< ReturnType["_def"]["shape"]>[key] >, any @@ -303,9 +308,9 @@ export function createTsForm< ? Omit< ComponentProps< Mapping[IndexOf< - Mapping, + UnwrapMapping, readonly [ - UnwrapZodType< + IndexOfUnwrapZodType< ReturnType< UnwrapEffects["_def"]["shape"] >[key] diff --git a/src/typeUtilities.ts b/src/typeUtilities.ts index eceead7..a3ead5c 100644 --- a/src/typeUtilities.ts +++ b/src/typeUtilities.ts @@ -1,3 +1,8 @@ +import { z } from "zod"; +import type { FormComponentMapping } from "./createSchemaForm"; +import { RTFBaseZodType, RTFSupportedZodTypes } from "./supportedZodTypes"; +import { UnwrapZodType } from "./unwrap"; + /** * @internal */ @@ -61,6 +66,31 @@ export type Indexes = { [K in Exclude>]: K; }; +/** + * @internal + */ +export type UnwrapZodBrand = T extends z.ZodBranded< + z.ZodTypeAny, + infer ID +> + ? ID + : T; + +/** + * @internal + */ +export type UnwrapMapping = { + [Index in keyof T]: T[Index] extends readonly [any, any] + ? readonly [UnwrapZodBrand, any] + : never; +}; + +/** + * @internal + */ +export type IndexOfUnwrapZodType = + T extends z.ZodBranded ? ID : UnwrapZodType; + /** * @internal */ From 6e5f8185225fb8a35288184c8d086273974a335f Mon Sep 17 00:00:00 2001 From: Isaac Way Date: Sat, 14 Jan 2023 11:56:56 -0600 Subject: [PATCH 2/2] fix issue with parameter type of createFieldSchema --- ...ema.test.ts => createFieldSchema.test.tsx} | 33 +++++++++++++++++++ src/createFieldSchema.tsx | 6 ++-- 2 files changed, 36 insertions(+), 3 deletions(-) rename src/__tests__/{createFieldSchema.test.ts => createFieldSchema.test.tsx} (68%) diff --git a/src/__tests__/createFieldSchema.test.ts b/src/__tests__/createFieldSchema.test.tsx similarity index 68% rename from src/__tests__/createFieldSchema.test.ts rename to src/__tests__/createFieldSchema.test.tsx index 996f0c5..2f805a4 100644 --- a/src/__tests__/createFieldSchema.test.ts +++ b/src/__tests__/createFieldSchema.test.tsx @@ -1,3 +1,4 @@ +import React from "react"; import { z } from "zod"; import { addHiddenProperties, @@ -30,6 +31,38 @@ describe("createFieldSchema", () => { createTsForm(mapping); }).not.toThrowError(); }); + it("should correctly type the form with multiple unique field schemas.", () => { + const A = createUniqueFieldSchema(z.string(), "one"); + const B = createUniqueFieldSchema(z.string(), "two"); + function In1(_: { req: string }) { + return
; + } + function In2(_: { req2: string }) { + return
; + } + const mapping = [ + [A, In1], + [B, In2], + ] as const; + + const Form = createTsForm(mapping); + + {}} + props={{ + a: { + req: "One", + }, + b: { + req2: "Two", + }, + }} + />; + }); }); describe("addHiddenProperties", () => { diff --git a/src/createFieldSchema.tsx b/src/createFieldSchema.tsx index f2a201f..f4632d5 100644 --- a/src/createFieldSchema.tsx +++ b/src/createFieldSchema.tsx @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z, ZodBranded } from "zod"; import { RTFSupportedZodTypes } from "./supportedZodTypes"; export const HIDDEN_ID_PROPERTY = "_rtf_id"; @@ -24,12 +24,12 @@ export function isSchemaWithHiddenProperties( export function addHiddenProperties< ID extends string, - T extends z.ZodBranded + T extends RTFSupportedZodTypes >(schema: T, properties: HiddenProperties) { for (const key in properties) { (schema as any)[key] = properties[key as keyof typeof properties]; } - return schema as T; + return schema as ZodBranded; } export function duplicateIdErrorMessage(id: string) {