From fb7a73c4998219cabd5c35b9d72ebef37726f07a Mon Sep 17 00:00:00 2001 From: Bob Reid Date: Mon, 26 May 2025 22:51:09 -0400 Subject: [PATCH 1/2] Implement support for application/json --- package-lock.json | 6 ++-- src/hook/index.test.tsx | 64 +++++++++++++++++++++++++++++++++++++++-- src/hook/index.tsx | 26 +++++++++++------ 3 files changed, 83 insertions(+), 13 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1e98e10..ff0d07c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "remix-hook-form", - "version": "5.1.1", + "version": "5.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "remix-hook-form", - "version": "5.1.1", + "version": "5.2.0", "license": "MIT", "workspaces": [ "src/testing-app", @@ -42,7 +42,7 @@ "@remix-run/react": "^2.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-hook-form": "^7.51.0" + "react-hook-form": "^7.55.0" } }, "node_modules/@ampproject/remapping": { diff --git a/src/hook/index.test.tsx b/src/hook/index.test.tsx index 2942700..c90edd5 100644 --- a/src/hook/index.test.tsx +++ b/src/hook/index.test.tsx @@ -9,6 +9,7 @@ import { import { RemixFormProvider, useRemixForm, useRemixFormContext } from "./index"; import React from "react"; import { useFetcher, type Navigation } from "@remix-run/react"; +import { object } from "zod"; const submitMock = vi.fn(); const fetcherSubmitMock = vi.fn(); @@ -16,9 +17,10 @@ const fetcherSubmitMock = vi.fn(); const useActionDataMock = vi.hoisted(() => vi.fn()); const useNavigationMock = vi.hoisted(() => - vi.fn<() => Pick>(() => ({ + vi.fn<() => Pick>(() => ({ state: "idle", formData: undefined, + json: undefined, })), ); @@ -249,12 +251,70 @@ describe("useRemixForm", () => { useNavigationMock.mockReturnValue({ state: "submitting", formData: new FormData(), + json: undefined, }); rerender(); expect(result.current.formState.isSubmitting).toBe(true); - useNavigationMock.mockReturnValue({ state: "idle", formData: undefined }); + useNavigationMock.mockReturnValue({ + state: "idle", + formData: undefined, + json: undefined, + }); + rerender(); + + expect(result.current.formState.isSubmitting).toBe(false); + }); + + it("should reset isSubmitting when the form is submitted using encType: application/json", async () => { + submitMock.mockReset(); + useNavigationMock.mockClear(); + + const { result, rerender } = renderHook(() => + useRemixForm({ + resolver: () => ({ values: {}, errors: {} }), + submitConfig: { + action: "/submit", + encType: "application/json", + }, + }), + ); + + expect(result.current.formState.isSubmitting).toBe(false); + + act(() => { + result.current.handleSubmit({} as any); + }); + expect(result.current.formState.isSubmitting).toBe(true); + + await waitFor(() => expect(submitMock).toHaveBeenCalledTimes(1)); + + expect(result.current.formState.isSubmitting).toBe(true); + + expect(submitMock).toHaveBeenCalledWith( + {}, + { + method: "post", + action: "/submit", + encType: "application/json", + }, + ); + + useNavigationMock.mockReturnValue({ + state: "submitting", + formData: undefined, + json: {}, + }); + rerender(); + + expect(result.current.formState.isSubmitting).toBe(true); + + useNavigationMock.mockReturnValue({ + state: "idle", + formData: undefined, + json: undefined, + }); rerender(); expect(result.current.formState.isSubmitting).toBe(false); diff --git a/src/hook/index.tsx b/src/hook/index.tsx index 33c118c..be364df 100644 --- a/src/hook/index.tsx +++ b/src/hook/index.tsx @@ -67,14 +67,24 @@ export const useRemixForm = ({ const methods = useForm({ ...formProps, errors: data?.errors }); const navigation = useNavigation(); // Either it's submitted to an action or submitted to a fetcher (or neither) - const isSubmittingForm = useMemo( - () => - Boolean( - (navigation.state !== "idle" && navigation.formData !== undefined) || - (fetcher?.state !== "idle" && fetcher?.formData !== undefined), - ), - [navigation.state, navigation.formData, fetcher?.state, fetcher?.formData], - ); + const isSubmittingForm = useMemo(() => { + const navigationIsSubmitting = + navigation.state !== "idle" && + (navigation.formData ?? navigation.json) !== undefined; + + const fetcherIsSubmitting = + fetcher?.state !== "idle" && + (fetcher?.formData ?? fetcher?.json) !== undefined; + + return navigationIsSubmitting || fetcherIsSubmitting; + }, [ + navigation.state, + navigation.formData, + navigation.json, + fetcher?.state, + fetcher?.formData, + fetcher?.json, + ]); // A state to keep track whether we're actually submitting the form through the network const [isSubmittingNetwork, setIsSubmittingNetwork] = useState(false); From 800396555559ccc81bff0da0d93f09f393a180a8 Mon Sep 17 00:00:00 2001 From: Bob Reid Date: Mon, 26 May 2025 22:56:28 -0400 Subject: [PATCH 2/2] remove unused import --- src/hook/index.test.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/hook/index.test.tsx b/src/hook/index.test.tsx index c90edd5..0bac466 100644 --- a/src/hook/index.test.tsx +++ b/src/hook/index.test.tsx @@ -9,7 +9,6 @@ import { import { RemixFormProvider, useRemixForm, useRemixFormContext } from "./index"; import React from "react"; import { useFetcher, type Navigation } from "@remix-run/react"; -import { object } from "zod"; const submitMock = vi.fn(); const fetcherSubmitMock = vi.fn();