diff --git a/src/atoms/_useFieldInitialValue.ts b/src/atoms/_useFieldInitialValue.ts index b120f61..8230b2f 100644 --- a/src/atoms/_useFieldInitialValue.ts +++ b/src/atoms/_useFieldInitialValue.ts @@ -24,9 +24,6 @@ export function _useFieldInitialValue( if (initialValue === undefined) { return; } - if (!store.get(field.dirty)) { - store.set(field.value, initialValue); - } - store.set(field._initialValue, initialValue); - }, [store, field._initialValue, field.value, field.dirty, initialValue]); + store.set(field.value, initialValue); + }, [store, field.value, initialValue]); } diff --git a/src/atoms/list-atom/listAtom.test.ts b/src/atoms/list-atom/listAtom.test.ts index c6ca351..0ca0cf7 100644 --- a/src/atoms/list-atom/listAtom.test.ts +++ b/src/atoms/list-atom/listAtom.test.ts @@ -272,11 +272,10 @@ describe("listAtom()", () => { expect(state.current.dirty).toBe(false); await act(async () => listActions.current.remove(list.current[0]!)); - expect(state.current.dirty).toBe(true); }); - it("becomes dirty when an item is added ", async () => { + it("becomes dirty when an item is added", async () => { const field = listAtom({ value: [], builder: (value) => numberField({ value }), @@ -288,7 +287,6 @@ describe("listAtom()", () => { expect(state.current.dirty).toBe(false); await act(async () => listActions.current.add()); - expect(state.current.dirty).toBe(true); }); @@ -306,10 +304,32 @@ describe("listAtom()", () => { expect(state.current.dirty).toBe(false); await act(async () => listActions.current.move(list.current[0]!)); - expect(state.current.dirty).toBe(true); }); + it("becomes dirty when some item field is edited", async () => { + const field = listAtom({ + value: [undefined], + builder: (value) => numberField({ value }), + }); + + const { result: fieldState } = renderHook(() => useFieldState(field)); + const { result: formFields } = renderHook(() => + useAtomValue(useAtomValue(field)._formFields), + ); + const { result: inputActions } = renderHook(() => + useFieldActions(formFields.current[0]!), + ); + + expect(fieldState.current.dirty).toBe(false); + + await act(async () => inputActions.current.setValue(42)); + expect(fieldState.current.dirty).toBe(true); + + await act(async () => inputActions.current.reset()); + expect(fieldState.current.dirty).toBe(false); + }); + it("becomes pristine when items are reordered & back", async () => { const field = listAtom({ value: [42, 84], @@ -325,54 +345,29 @@ describe("listAtom()", () => { // moves first item down await act(async () => listActions.current.move(list.current[0]!)); - expect(state.current.dirty).toBe(true); // moves first item down await act(async () => listActions.current.move(list.current[0]!)); - expect(state.current.dirty).toBe(false); }); it("becomes pristine after value is set (the set is usually called by useFieldInitialValue to hydrate the field)", async () => { const field = listAtom({ - value: [] as number[], + value: [1, 2], builder: (value) => numberField({ value }), }); const { result: state } = renderHook(() => useFieldState(field)); const { result: fieldActions } = renderHook(() => useFieldActions(field)); - expect(state.current.dirty).toBe(false); - - await act(async () => fieldActions.current.setValue([42])); + // make list dirty + const { result: listActions } = renderHook(() => useListActions(field)); + await act(async () => listActions.current.add()); + expect(state.current.dirty).toBe(true); + await act(async () => fieldActions.current.setValue([42, 84])); expect(state.current.dirty).toBe(false); }); - - it("becomes dirty when some item field is edited", async () => { - const field = listAtom({ - value: [undefined], - builder: (value) => numberField({ value }), - }); - - const { result: fieldState } = renderHook(() => useFieldState(field)); - const { result: formFields } = renderHook(() => - useAtomValue(useAtomValue(field)._formFields), - ); - const { result: inputActions } = renderHook(() => - useFieldActions(formFields.current[0]!), - ); - - expect(fieldState.current.dirty).toBe(false); - - await act(async () => inputActions.current.setValue(42)); - - expect(fieldState.current.dirty).toBe(true); - - await act(async () => inputActions.current.reset()); - - expect(fieldState.current.dirty).toBe(false); - }); }); }); diff --git a/src/hooks/use-list-field-initial-value/index.ts b/src/hooks/use-list-field-initial-value/index.ts new file mode 100644 index 0000000..b9e95b3 --- /dev/null +++ b/src/hooks/use-list-field-initial-value/index.ts @@ -0,0 +1 @@ +export * from "./useListFieldInitialValue"; diff --git a/src/hooks/use-list-field-initial-value/useListFieldInitialValue.test.ts b/src/hooks/use-list-field-initial-value/useListFieldInitialValue.test.ts new file mode 100644 index 0000000..e7e2e61 --- /dev/null +++ b/src/hooks/use-list-field-initial-value/useListFieldInitialValue.test.ts @@ -0,0 +1,42 @@ +import { act, renderHook } from "@testing-library/react"; +import { useFieldState } from "form-atoms"; +import { describe, expect, it } from "vitest"; + +import { useListFieldInitialValue } from "./useListFieldInitialValue"; +import { listAtom } from "../../atoms"; +import { numberField } from "../../fields"; +import { useListActions } from "../use-list-actions"; + +describe("useListFieldInitialValue()", () => { + it("reinitializes the field value", async () => { + const field = listAtom({ + value: [] as number[], + builder: (value) => numberField({ value }), + }); + + const { result: state } = renderHook(() => useFieldState(field)); + const { rerender } = renderHook( + (props) => useListFieldInitialValue(field, props.initialValue), + { initialProps: { initialValue: [1, 2] } }, + ); + + // make list dirty + const { result: listActions } = renderHook(() => useListActions(field)); + await act(async () => listActions.current.add()); + expect(state.current.dirty).toBe(true); + + const initialValue = [42, 84]; + + // initialization makes field pristine + rerender({ initialValue }); + expect(state.current.dirty).toBe(false); + + // make list dirty again + await act(async () => listActions.current.add()); + expect(state.current.dirty).toBe(true); + + // re-inititialation skipped with the same initialValue (useEffect dependency) + rerender({ initialValue }); + expect(state.current.dirty).toBe(true); + }); +}); diff --git a/src/hooks/use-list-field/useListFieldInitialValue.ts b/src/hooks/use-list-field-initial-value/useListFieldInitialValue.ts similarity index 100% rename from src/hooks/use-list-field/useListFieldInitialValue.ts rename to src/hooks/use-list-field-initial-value/useListFieldInitialValue.ts diff --git a/src/hooks/use-list-field/index.ts b/src/hooks/use-list-field/index.ts index 09c4d10..a8bb3cb 100644 --- a/src/hooks/use-list-field/index.ts +++ b/src/hooks/use-list-field/index.ts @@ -1,2 +1 @@ export * from "./useListField"; -export * from "./useListFieldInitialValue"; diff --git a/src/hooks/use-list-field/useListField.ts b/src/hooks/use-list-field/useListField.ts index b4334b5..ddea11a 100644 --- a/src/hooks/use-list-field/useListField.ts +++ b/src/hooks/use-list-field/useListField.ts @@ -1,10 +1,10 @@ import { UseFieldOptions } from "form-atoms"; import { useAtomValue } from "jotai"; -import { useListFieldInitialValue } from "./useListFieldInitialValue"; import { ListAtomItems, ListAtomValue } from "../../atoms/list-atom"; import { ListField } from "../../fields"; import { useListActions } from "../use-list-actions"; +import { useListFieldInitialValue } from "../use-list-field-initial-value"; export const useListField = < Fields extends ListAtomItems,