Skip to content

Commit

Permalink
fix(list-atom) (#108)
Browse files Browse the repository at this point in the history
* fix(list-atom): make pristine (!dirty) after new initialValue is set (#107)

Fixes #105
This requires the user to control the referential equality of the initialValue for the same values in order to prevent unnecessary updates.

* chore(release): 4.0.13-next.1

## [4.0.13-next.1](v4.0.12...v4.0.13-next.1) (2024-02-14)

### Bug Fixes

* **list-atom:** make pristine (!dirty) after new initialValue is set ([#107](#107)) ([170312d](170312d)), closes [#105](#105)

---------

Co-authored-by: semantic-release-bot <semantic-release-bot@martynus.net>
  • Loading branch information
MiroslavPetrik and semantic-release-bot committed Feb 14, 2024
1 parent 46d4753 commit 4b35fd3
Show file tree
Hide file tree
Showing 8 changed files with 83 additions and 42 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## [4.0.13-next.1](https://github.com/form-atoms/field/compare/v4.0.12...v4.0.13-next.1) (2024-02-14)


### Bug Fixes

* **list-atom:** make pristine (!dirty) after new initialValue is set ([#107](https://github.com/form-atoms/field/issues/107)) ([170312d](https://github.com/form-atoms/field/commit/170312d952cb4a7e72eebb8ba2a2606feb0cbdbf)), closes [#105](https://github.com/form-atoms/field/issues/105)

## [4.0.12](https://github.com/form-atoms/field/compare/v4.0.11...v4.0.12) (2024-02-14)


Expand Down
7 changes: 2 additions & 5 deletions src/atoms/_useFieldInitialValue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,6 @@ export function _useFieldInitialValue<Value>(
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]);
}
65 changes: 30 additions & 35 deletions src/atoms/list-atom/listAtom.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 }),
Expand All @@ -288,7 +287,6 @@ describe("listAtom()", () => {
expect(state.current.dirty).toBe(false);

await act(async () => listActions.current.add());

expect(state.current.dirty).toBe(true);
});

Expand All @@ -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],
Expand All @@ -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);
});
});
});
1 change: 1 addition & 0 deletions src/hooks/use-list-field-initial-value/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./useListFieldInitialValue";
Original file line number Diff line number Diff line change
@@ -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);
});
});
1 change: 0 additions & 1 deletion src/hooks/use-list-field/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export * from "./useListField";
export * from "./useListFieldInitialValue";
2 changes: 1 addition & 1 deletion src/hooks/use-list-field/useListField.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down

0 comments on commit 4b35fd3

Please sign in to comment.