From 8521ef3b4f693671033d39f6cde745b4bcecc4c7 Mon Sep 17 00:00:00 2001 From: Ali Khowaja <96657278+phyziyx@users.noreply.github.com> Date: Mon, 20 Oct 2025 13:10:23 +0500 Subject: [PATCH 1/6] Fix: Repeating keys in form data --- src/utilities/index.ts | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/utilities/index.ts b/src/utilities/index.ts index 486f71c..b94a547 100644 --- a/src/utilities/index.ts +++ b/src/utilities/index.ts @@ -25,8 +25,17 @@ export const generateFormData = ( // biome-ignore lint/suspicious/noExplicitAny: const outputObject: Record = {}; + // See if a key is repeated, and then handle that in a special case + const keyCounts: Records 0) { + if (!currentObject[key]) { + currentObject[key] = []; + } + currentObject[key].push(data); + } else { + currentObject[lastKeyPart] = data; + } } } } From 3ed9c71971d719734eb2e6a5b27455656987ad11 Mon Sep 17 00:00:00 2001 From: Ali Khowaja <96657278+phyziyx@users.noreply.github.com> Date: Mon, 20 Oct 2025 15:59:38 +0500 Subject: [PATCH 2/6] Add test for multiple file input handling Added a test case to handle multiple file inputs in the form data --- src/utilities/index.test.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/utilities/index.test.ts b/src/utilities/index.test.ts index 1f7e205..b39d1c6 100644 --- a/src/utilities/index.test.ts +++ b/src/utilities/index.test.ts @@ -132,6 +132,22 @@ describe("parseFormData", () => { }); }); + it("should handle multiple file input", async () => { + const request = new Request("http://localhost:3000"); + const requestFormDataSpy = vi.spyOn(request, "formData"); + const blob = new Blob(["Hello, world!"], { type: "text/plain" }); + const mockFormData = new FormData(); + mockFormData.append("files", blob); + mockFormData.append("files", blob); + requestFormDataSpy.mockResolvedValueOnce(mockFormData); + const data = await parseFormData<{ files[]: Blob }>(request); + expect(data.files).toBeTypeOf("object"); + expect(Array.isArray(data.files)).toBe(true); + expect(data.files).toHaveLength(2); + expect(data.files[0]).toBeInstanceOf(File); + expect(data.files[1]).toBeInstanceOf(File); + }); + it("should not throw an error when a file is passed in", async () => { const request = new Request("http://localhost:3000"); const requestFormDataSpy = vi.spyOn(request, "formData"); From 680ce50eff1d30c56573cc04076271c889fce8fb Mon Sep 17 00:00:00 2001 From: Ali Khowaja <96657278+phyziyx@users.noreply.github.com> Date: Mon, 20 Oct 2025 16:08:05 +0500 Subject: [PATCH 3/6] Fix: keyCount value typo --- src/utilities/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utilities/index.ts b/src/utilities/index.ts index b94a547..4b1d11a 100644 --- a/src/utilities/index.ts +++ b/src/utilities/index.ts @@ -77,7 +77,7 @@ export const generateFormData = ( } // Otherwise, set a property on the current object with the last key part and the corresponding value. else { - if (keyCount > 0) { + if (keyCount > 1) { if (!currentObject[key]) { currentObject[key] = []; } From 7d510f735a87f8804b221b5714a10dd1fd5ec690 Mon Sep 17 00:00:00 2001 From: phyziyx <96657278+phyziyx@users.noreply.github.com> Date: Mon, 20 Oct 2025 22:22:39 +0500 Subject: [PATCH 4/6] fix: errors --- package-lock.json | 4 ++-- src/utilities/index.test.ts | 2 +- src/utilities/index.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 96418cf..00c6822 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "remix-hook-form", - "version": "7.0.1", + "version": "7.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "remix-hook-form", - "version": "7.0.1", + "version": "7.1.0", "license": "MIT", "workspaces": [ ".", diff --git a/src/utilities/index.test.ts b/src/utilities/index.test.ts index b39d1c6..083122f 100644 --- a/src/utilities/index.test.ts +++ b/src/utilities/index.test.ts @@ -140,7 +140,7 @@ describe("parseFormData", () => { mockFormData.append("files", blob); mockFormData.append("files", blob); requestFormDataSpy.mockResolvedValueOnce(mockFormData); - const data = await parseFormData<{ files[]: Blob }>(request); + const data = await parseFormData<{ files: Blob[] }>(request); expect(data.files).toBeTypeOf("object"); expect(Array.isArray(data.files)).toBe(true); expect(data.files).toHaveLength(2); diff --git a/src/utilities/index.ts b/src/utilities/index.ts index 4b1d11a..b2f830b 100644 --- a/src/utilities/index.ts +++ b/src/utilities/index.ts @@ -26,8 +26,8 @@ export const generateFormData = ( const outputObject: Record = {}; // See if a key is repeated, and then handle that in a special case - const keyCounts: Records = {}; + for (const key of formData.keys()) { keyCounts[key] = (keyCounts[key] ?? 0) + 1; } From 96c21e42e16efd35fcce0fca53a7188521ef60a6 Mon Sep 17 00:00:00 2001 From: phyziyx <96657278+phyziyx@users.noreply.github.com> Date: Mon, 20 Oct 2025 22:55:59 +0500 Subject: [PATCH 5/6] add: multiple file input to test app --- test-apps/react-router/app/routes/home.tsx | 24 ++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/test-apps/react-router/app/routes/home.tsx b/test-apps/react-router/app/routes/home.tsx index 68e8bb6..f0f00bd 100644 --- a/test-apps/react-router/app/routes/home.tsx +++ b/test-apps/react-router/app/routes/home.tsx @@ -13,6 +13,15 @@ import { import { getFormData, getValidatedFormData } from "remix-hook-form/middleware"; import { z } from "zod"; +const fileListSchema = z.any().refine((files) => { + return ( + typeof files === "object" && + Symbol.iterator in files && + !Array.isArray(files) && + Array.from(files).every((file: any) => file instanceof File) + ); +}); + const FormDataZodSchema = z.object({ email: z.string().trim().nonempty("validation.required"), password: z.string().trim().nonempty("validation.required"), @@ -21,6 +30,7 @@ const FormDataZodSchema = z.object({ boolean: z.boolean().optional(), date: z.date().or(z.string()), null: z.null(), + files: fileListSchema.optional(), }); const resolver = zodResolver(FormDataZodSchema); @@ -38,6 +48,13 @@ export const action = async ({ context }: ActionFunctionArgs) => { if (errors) { return { errors, receivedValues }; } + + console.log( + "File names:", + // since files is of type "any", we need to assert its type here + data.files?.map((file: File) => file.name).join(", "), + ); + return { result: "success" }; }; @@ -54,6 +71,7 @@ export default function Index() { date: new Date(), boolean: true, null: null, + files: undefined, }, submitData: { test: "test" }, @@ -73,6 +91,12 @@ export default function Index() { +
From bab6fc2a846f4029175f7602810aaf59fa1cd38c Mon Sep 17 00:00:00 2001 From: phyziyx <96657278+phyziyx@users.noreply.github.com> Date: Mon, 20 Oct 2025 23:18:15 +0500 Subject: [PATCH 6/6] add: multiple selection inputs to test app --- test-apps/react-router/app/routes/home.tsx | 46 ++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/test-apps/react-router/app/routes/home.tsx b/test-apps/react-router/app/routes/home.tsx index f0f00bd..d278a7d 100644 --- a/test-apps/react-router/app/routes/home.tsx +++ b/test-apps/react-router/app/routes/home.tsx @@ -31,6 +31,8 @@ const FormDataZodSchema = z.object({ date: z.date().or(z.string()), null: z.null(), files: fileListSchema.optional(), + options: z.array(z.string()).optional(), + checkboxes: z.array(z.string()).optional(), }); const resolver = zodResolver(FormDataZodSchema); @@ -55,6 +57,8 @@ export const action = async ({ context }: ActionFunctionArgs) => { data.files?.map((file: File) => file.name).join(", "), ); + console.log("Selected options:", data.options); + return { result: "success" }; }; @@ -72,6 +76,8 @@ export default function Index() { boolean: true, null: null, files: undefined, + options: undefined, + checkboxes: undefined, }, submitData: { test: "test" }, @@ -98,6 +104,46 @@ export default function Index() { {formState.errors.files?.message} + +