Skip to content

Commit 79d9b77

Browse files
committed
fix(react-hook-form): initialize useFieldArray via reset on first data load (#7401)
1 parent 6d227f3 commit 79d9b77

3 files changed

Lines changed: 71 additions & 2 deletions

File tree

.changeset/curvy-meals-smash.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@refinedev/react-hook-form": minor
3+
---
4+
5+
Fix useFieldArray.fields empty on subsequent loads

packages/react-hook-form/src/useForm/index.spec.tsx

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import type { IRefineOptions, HttpError } from "@refinedev/core";
55
import * as Core from "@refinedev/core";
66
import { MockJSONServer, TestWrapper, act, render, waitFor } from "../../test";
77
import { Route, Routes } from "react-router";
8-
import { Controller } from "react-hook-form";
8+
import { Controller, useFieldArray } from "react-hook-form";
9+
import { screen } from "@testing-library/react";
910

1011
interface IPost {
1112
title: string;
@@ -329,4 +330,56 @@ describe("useForm hook", () => {
329330
useFormCoreSpy.mockRestore();
330331
}
331332
});
333+
334+
it("should populate useFieldArray fields that mount after initial query data sync", async () => {
335+
const queryData = {
336+
data: {
337+
data: { id: "1", tags: [{ value: "react" }, { value: "refine" }] },
338+
},
339+
};
340+
341+
const useFormCoreSpy = vi.spyOn(Core, "useForm").mockReturnValue({
342+
query: queryData,
343+
onFinish: vi.fn().mockResolvedValue({}),
344+
onFinishAutoSave: vi.fn().mockResolvedValue({}),
345+
formLoading: true, // cached data available immediately; formLoading driven by EditPage prop
346+
});
347+
348+
const FieldArrayChild = ({ control }: { control: any }) => {
349+
const { fields } = useFieldArray({ control, name: "tags" });
350+
return (
351+
<ul>
352+
{fields.map((field) => (
353+
<li key={field.id} data-testid="tag-item" />
354+
))}
355+
</ul>
356+
);
357+
};
358+
359+
const EditPage = ({ formLoading }: { formLoading: boolean }) => {
360+
const { control } = useForm<IPost, HttpError, IPost>({
361+
refineCoreProps: { resource: "posts", action: "edit", id: "1" }
362+
});
363+
if (formLoading) return <p>loading</p>;
364+
return <FieldArrayChild control={control} />;
365+
};
366+
367+
try {
368+
const page = (formLoading: boolean) => (
369+
<Routes>
370+
<Route path="/" element={<EditPage formLoading={formLoading} />} />
371+
</Routes>
372+
);
373+
374+
const { rerender } = render(page(true), { wrapper: TestWrapper({}) });
375+
376+
await act(async () => rerender(page(false)));
377+
378+
await waitFor(() =>
379+
expect(screen.getAllByTestId("tag-item")).toHaveLength(2),
380+
);
381+
} finally {
382+
useFormCoreSpy.mockRestore();
383+
}
384+
});
332385
});

packages/react-hook-form/src/useForm/index.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ export const useForm = <
129129
watch,
130130
setValue,
131131
getValues,
132+
reset,
132133
handleSubmit: handleSubmitReactHookForm,
133134
setError,
134135
formState: { dirtyFields },
@@ -140,6 +141,8 @@ export const useForm = <
140141
const syncedFieldsRef = React.useRef<Set<string>>(new Set());
141142
// Track mounted field names so late-registered fields can be detected.
142143
const mountedFieldsRef = React.useRef<Set<string>>(new Set());
144+
// Track whether we have called reset() on initial load, future loads use applyValuesToFields instead to prevent wiping metadata.
145+
const hasResetRef = React.useRef(false);
143146

144147
const useFormCoreResult = useFormCore<
145148
TQueryFnData,
@@ -268,7 +271,15 @@ export const useForm = <
268271
const applyQueryValues = () => {
269272
if (!isActive) return;
270273

271-
applyValuesToFields(getRegisteredFields(), data, false);
274+
if (!hasResetRef.current) {
275+
hasResetRef.current = true;
276+
reset(
277+
{ ...getValues(), ...data } as unknown as TVariables,
278+
{ keepDirtyValues: true },
279+
);
280+
} else {
281+
applyValuesToFields(getRegisteredFields(), data, false);
282+
}
272283
};
273284

274285
queryDataRef.current = data;

0 commit comments

Comments
 (0)