diff --git a/package-lock.json b/package-lock.json index c7fae40..96418cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "remix-hook-form", - "version": "7.0.0", + "version": "7.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "remix-hook-form", - "version": "7.0.0", + "version": "7.0.1", "license": "MIT", "workspaces": [ ".", diff --git a/src/hook/index.test.tsx b/src/hook/index.test.tsx index bb38cad..0e9b11b 100644 --- a/src/hook/index.test.tsx +++ b/src/hook/index.test.tsx @@ -16,9 +16,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, })), ); @@ -290,12 +291,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 bc5a91a..2722eed 100644 --- a/src/hook/index.tsx +++ b/src/hook/index.tsx @@ -78,14 +78,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);