Skip to content
This repository has been archived by the owner on Jul 27, 2022. It is now read-only.

Commit

Permalink
fix(useFieldArray): field array in field array not working
Browse files Browse the repository at this point in the history
  • Loading branch information
wellyshen committed Aug 2, 2021
1 parent e3eb173 commit 359e4cc
Show file tree
Hide file tree
Showing 7 changed files with 139 additions and 62 deletions.
5 changes: 5 additions & 0 deletions .changeset/mighty-wasps-speak.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"react-cool-form": patch
---

fix(useFieldArray): nested field array not working
97 changes: 83 additions & 14 deletions app/src/Playground/index.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,92 @@
/* eslint-disable no-console */

import { useForm } from "react-cool-form";
import { useForm, useFieldArray } from "react-cool-form";

export default () => {
const { form, runValidation } = useForm({
// validate: () => ({ foo: "Required" }),
focusOnError: ["foo"],
const { form } = useForm({
defaultValues: {
foo: [
{
name: "Iron Man",
arr: [{ name: "iron arr.0" }, { name: "iron arr.1" }],
},
],
},
onSubmit: (values) => console.log("LOG ===> Form data: ", values),
});
const [fields, { push, insert, remove }] = useFieldArray("foo");

return (
<>
<form ref={form} noValidate>
<input name="foo" required />
<input name="bar" required />
{/* <input type="submit" /> */}
</form>
<button type="button" onClick={() => runValidation(["bar"])}>
Validate
</button>
</>
<form ref={form}>
<table>
<thead>
<tr>
<th>Name</th>
<th>Arr</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{fields.map((fieldName, index) => (
<tr key={fieldName}>
<td>
<input name={`${fieldName}.name`} />
</td>
<td>
<Arr field={fieldName} />
</td>
<td>
<button type="button" onClick={() => remove(index)}>
REMOVE
</button>
</td>
</tr>
))}
</tbody>
</table>
<div>
<button
type="button"
onClick={() => {
push({ name: "Loki", arr: [{ name: "Your Savior Is Here" }] });
}}
>
PUSH
</button>
<button
type="button"
onClick={() =>
insert(0, {
name: "Spider Man",
arr: [{ name: "Your Friendly Neighborhood Spider-Man" }],
})
}
>
INSERT
</button>
</div>
<input type="submit" />
<input type="reset" />
</form>
);
};

function Arr({ field }: any) {
const [fields, { push }] = useFieldArray(`${field}.arr`);

return (
<div>
{fields.map((fieldName) => (
<input
key={fieldName}
style={{ height: "20px", width: "100px" }}
type="text"
name={`${fieldName}.name`}
/>
))}
<button type="button" onClick={() => push({ name: "xxx" })}>
inner push
</button>
</div>
);
}
2 changes: 1 addition & 1 deletion src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export type Fields = Map<

export type Parsers = ObjMap<Omit<FieldOptions, "validate">>;

export type FieldArray = ObjMap<{ fields: ObjMap; reset: () => void }>;
export type FieldArray = Map<string, { fields: ObjMap; reset: () => void }>;

interface EventOptions<V> {
removeField: RemoveField;
Expand Down
40 changes: 18 additions & 22 deletions src/useFieldArray.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,13 @@ export default <T = any, V extends FormValues = FormValues>(
const [fields, setFields] = useState<string[]>(getFields(true));

const updateFields = useCallback(() => {
setFields(getFields());
setNodesOrValues(getState("values"), {
shouldSetValues: false,
fields: Object.keys(fieldArrayRef.current[name].fields),
});
if (fieldArrayRef.current.has(name)) {
setFields(getFields());
setNodesOrValues(getState("values"), {
shouldSetValues: false,
fields: Object.keys(fieldArrayRef.current.get(name)!.fields),
});
}
}, [fieldArrayRef, getFields, getState, name, setNodesOrValues]);

useEffect(() => {
Expand All @@ -83,16 +85,13 @@ export default <T = any, V extends FormValues = FormValues>(
}

return () => {
if (shouldRemoveField(name)) removeField(name);
if (shouldRemoveField(name)) removeField(name, ["defaultValue"]);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

if (!fieldArrayRef.current[name])
fieldArrayRef.current[name] = {
reset: updateFields,
fields: {},
};
if (!fieldArrayRef.current.has(name))
fieldArrayRef.current.set(name, { reset: updateFields, fields: {} });
if (validate) fieldValidatorsRef.current[name] = validate;

const setState = useCallback(
Expand All @@ -106,8 +105,8 @@ export default <T = any, V extends FormValues = FormValues>(
let state = getState();

(["values", "touched", "errors", "dirty"] as Keys[]).forEach((key) => {
const value = state[key][name];
const fieldsLength = state.values[name]?.length;
const value = get(state[key], name);
const fieldsLength = get(state.values, name)?.length;

if (
key === "values" ||
Expand All @@ -117,15 +116,12 @@ export default <T = any, V extends FormValues = FormValues>(
)
state = set(
state,
key,
{
...state[key],
[name]: handler(
Array.isArray(value) ? [...value] : [],
key,
fieldsLength ? fieldsLength - 1 : 0
),
},
`${key}.${name}`,
handler(
Array.isArray(value) ? [...value] : [],
key,
fieldsLength ? fieldsLength - 1 : 0
),
true
);
});
Expand Down
17 changes: 8 additions & 9 deletions src/useForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export default <V extends FormValues = FormValues>({
const formRef = useRef<HTMLElement>();
const fieldsRef = useRef<Fields>(new Map());
const fieldParsersRef = useRef<Parsers>({});
const fieldArrayRef = useRef<FieldArray>({});
const fieldArrayRef = useRef<FieldArray>(new Map());
const controlsRef = useRef<ObjMap>({});
const formValidatorRef = useLatest(validate);
const fieldValidatorsRef = useRef<ObjMap<FieldValidator<V>>>({});
Expand Down Expand Up @@ -183,7 +183,7 @@ export default <V extends FormValues = FormValues>({
const fieldArrayName = isFieldArray(fieldArrayRef.current, name);

if (fieldArrayName)
fieldArrayRef.current[fieldArrayName].fields[name] = true;
fieldArrayRef.current.get(fieldArrayName)!.fields[name] = true;

acc.set(name, {
...acc.get(name),
Expand Down Expand Up @@ -292,7 +292,7 @@ export default <V extends FormValues = FormValues>({
name,
value,
shouldUpdateDefaultValue = !isFieldArray(fieldArrayRef.current, name) ||
!isUndefined(get(initialStateRef.current.values, name.split(".")[0]))
!isUndefined(get(initialStateRef.current.values, name))
) => {
if (shouldUpdateDefaultValue)
initialStateRef.current.values = set(
Expand Down Expand Up @@ -704,7 +704,7 @@ export default <V extends FormValues = FormValues>({
setNodeValue(name, value);

isFieldArray(fieldArrayRef.current, name, (key) =>
fieldArrayRef.current[key].reset()
fieldArrayRef.current.get(key)!.reset()
);

if (shouldTouched) setTouched(name, true, { shouldValidate: false });
Expand Down Expand Up @@ -774,7 +774,7 @@ export default <V extends FormValues = FormValues>({
setStateRef("", state);
onResetRef.current(state.values, getOptions(), e);

Object.values(fieldArrayRef.current).forEach((field) => field.reset());
fieldArrayRef.current.forEach((field) => field.reset());
},
[getOptions, onResetRef, setNodesOrValues, setStateRef, stateRef]
);
Expand Down Expand Up @@ -842,8 +842,8 @@ export default <V extends FormValues = FormValues>({
? removeOnUnmounted
: [
...Array.from(fieldsRef.current.keys()),
...Array.from(fieldArrayRef.current.keys()),
...Object.keys(controlsRef.current),
...Object.keys(fieldArrayRef.current),
];
names = isFunction(removeOnUnmounted) ? removeOnUnmounted(names) : names;

Expand Down Expand Up @@ -882,10 +882,9 @@ export default <V extends FormValues = FormValues>({

delete fieldParsersRef.current[name];
delete fieldValidatorsRef.current[name];
delete fieldArrayRef.current[name];
delete controlsRef.current[name];

if (fieldsRef.current.has(name)) fieldsRef.current.delete(name);
fieldArrayRef.current.delete(name);
fieldsRef.current.delete(name);
},
[handleUnset, stateRef]
);
Expand Down
22 changes: 14 additions & 8 deletions src/utils/isFieldArray.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,26 @@ import isFieldArray from "./isFieldArray";

describe("isFieldArray", () => {
it("should work correctly", () => {
// @ts-expect-error
expect(isFieldArray({ foo: true }, "foo[0].a")).toBe("foo");
const value = { fields: {}, reset: () => null };
const foo = new Map([
["foo", value],
["foo[0].a[0]", value],
]);

// @ts-expect-error
expect(isFieldArray({ foo: true }, "bar[0].a")).toBeUndefined();
expect(isFieldArray(foo, "foo[0].a")).toBe("foo");

expect(isFieldArray(foo, "foo[0].a[0].b")).toBe("foo[0].a[0]");

expect(isFieldArray(foo, "bar[0].a")).toBeUndefined();

let callback = jest.fn();
// @ts-expect-error
isFieldArray({ foo: true }, "foo[0].a", callback);

isFieldArray(foo, "foo[0].a", callback);
expect(callback).toHaveBeenCalledWith("foo");

callback = jest.fn();
// @ts-expect-error
isFieldArray({ foo: true }, "bar[0].a", callback);

isFieldArray(foo, "bar[0].a", callback);
expect(callback).not.toHaveBeenCalled();
});
});
18 changes: 10 additions & 8 deletions src/utils/isFieldArray.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@ export default (
): string | void => {
let fieldName;

Object.keys(fields).some((key) => {
if (name.startsWith(key)) {
fieldName = key;
if (callback) callback(key);
return true;
}
return false;
});
Array.from(fields)
.reverse()
.some(([key]) => {
if (name.startsWith(key)) {
fieldName = key;
if (callback) callback(key);
return true;
}
return false;
});

return fieldName;
};

0 comments on commit 359e4cc

Please sign in to comment.