Skip to content

Commit

Permalink
Add ignoreUnregister form config. final-form#474
Browse files Browse the repository at this point in the history
  • Loading branch information
Dragomir-Ivanov committed Jun 28, 2023
1 parent d20c44b commit 15d5e7d
Show file tree
Hide file tree
Showing 8 changed files with 214 additions and 2 deletions.
10 changes: 10 additions & 0 deletions docs/types/Config.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,16 @@ boolean

If `true`, the value of a field will be destroyed when that field is unregistered. Defaults to `false`. Can be useful when creating dynamic forms where only form values displayed need be submitted.

## `ignoreUnregister`

Optional.

```ts
boolean
```

If `true`, the value and state of a field will be preserved when that field is unregistered. Defaults to `false`. Can be useful when hiding part of the form, as an optimization. Mainly to be used by JS framework wrappers, such as `react-final-form`.

## `initialValues`

```ts
Expand Down
8 changes: 8 additions & 0 deletions docs/types/FormApi.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@ boolean

A read/write property to get and set the `destroyOnUnregister` config setting.

## `ignoreUnregister`

```ts
boolean
```

A read/write property to get and set the `ignoreUnregister` config setting.

## `focus`

```ts
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@
},
{
"path": "dist/final-form.cjs.js",
"maxSize": "10.5kB"
"maxSize": "10.6kB"
}
],
"dependencies": {
Expand Down
82 changes: 82 additions & 0 deletions src/FinalForm.fieldSubscribing.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -912,4 +912,86 @@ describe("Field.subscribing", () => {
expect(name1).toHaveBeenCalledTimes(1);
expect(name2).toHaveBeenCalledTimes(1);
});

it("should loose field state on unregister when ignoreUnregister is false", () => {
const form = createForm({
onSubmit: onSubmitMock,
});

const subscriber = jest.fn();
let unregister = form.registerField("foo", subscriber, { value: true });

// initial state
expect(form.getFieldState("foo").dirty).toBe(false);
expect(form.getFieldState("foo").touched).toBe(false);
expect(form.getFieldState("foo").modified).toBe(false);

// change value
form.change("foo", "bar");
form.blur("foo");

// state changed
expect(form.getFieldState("foo").dirty).toBe(true);
expect(form.getFieldState("foo").touched).toBe(true);
expect(form.getFieldState("foo").modified).toBe(true);

// unregister should remove state
unregister();

// field state is lost
expect(form.getFieldState("foo")).toBe(undefined);

// re-register the same field
unregister = form.registerField("foo", subscriber, { value: true });

// dirty state is preserved, because FF doesn't touch the value on unregister
expect(form.getFieldState("foo").dirty).toBe(true);

// however the other states are reset
expect(form.getFieldState("foo").touched).toBe(false);
expect(form.getFieldState("foo").modified).toBe(false);
});

it("should keep field state on unregister when ignoreUnregister is true", () => {
const form = createForm({
onSubmit: onSubmitMock,
ignoreUnregister: true,
});

const subscriber = jest.fn();
let unregister = form.registerField("foo", subscriber, { value: true });

// initial state
expect(form.getFieldState("foo").dirty).toBe(false);
expect(form.getFieldState("foo").touched).toBe(false);
expect(form.getFieldState("foo").modified).toBe(false);

// change value
form.change("foo", "bar");
form.blur("foo");

// state changed
expect(form.getFieldState("foo").dirty).toBe(true);
expect(form.getFieldState("foo").touched).toBe(true);
expect(form.getFieldState("foo").modified).toBe(true);

// unregister should remove state
unregister();

// field state is preserved
expect(form.getFieldState("foo")).not.toBe(undefined);
expect(form.getFieldState("foo").dirty).toBe(true);
expect(form.getFieldState("foo").touched).toBe(true);
expect(form.getFieldState("foo").modified).toBe(true);

// re-register the same field
unregister = form.registerField("foo", subscriber, { value: true });

// dirty state is preserved, because FF doesn't touch the value on unregister
expect(form.getFieldState("foo").dirty).toBe(true);

// however the other states are reset
expect(form.getFieldState("foo").touched).toBe(true);
expect(form.getFieldState("foo").modified).toBe(true);
});
});
13 changes: 12 additions & 1 deletion src/FinalForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ function createForm<FormValues: FormValuesShape>(
let {
debug,
destroyOnUnregister,
ignoreUnregister,
keepDirtyOnReinitialize,
initialValues,
mutators,
Expand Down Expand Up @@ -757,6 +758,13 @@ function createForm<FormValues: FormValuesShape>(
destroyOnUnregister = value;
},

get ignoreUnregister() {
return !!ignoreUnregister;
},
set ignoreUnregister(value: boolean) {
ignoreUnregister = value;
},

focus: (name: string) => {
const field = state.fields[name];
if (field && !field.active) {
Expand Down Expand Up @@ -950,7 +958,7 @@ function createForm<FormValues: FormValuesShape>(
let lastOne =
hasFieldSubscribers &&
!Object.keys(state.fieldSubscribers[name].entries).length;
if (lastOne) {
if (lastOne && !ignoreUnregister) {
delete state.fieldSubscribers[name];
delete state.fields[name];
if (validatorRemoved) {
Expand Down Expand Up @@ -1058,6 +1066,9 @@ function createForm<FormValues: FormValuesShape>(
case "destroyOnUnregister":
destroyOnUnregister = value;
break;
case "ignoreUnregister":
ignoreUnregister = value;
break;
case "initialValues":
api.initialize(value);
break;
Expand Down
96 changes: 96 additions & 0 deletions src/FinalForm.setConfig.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -492,4 +492,100 @@ describe("FinalForm.setConfig", () => {
expect(foz).toHaveBeenCalledTimes(2); // but field not notified
expect(form.destroyOnUnregister).toBe(true);
});

it('should update ignoreUnregister on setConfig("ignoreUnregister", value)', () => {
const form = createForm({
onSubmit: onSubmitMock,
ignoreUnregister: true,
});

const subscriber = jest.fn();
let unregister = form.registerField("foo", subscriber, { value: true });

// initial state
expect(form.getFieldState("foo").dirty).toBe(false);
expect(form.getFieldState("foo").touched).toBe(false);
expect(form.getFieldState("foo").modified).toBe(false);

// change value
form.change("foo", "bar");
form.blur("foo");

// state changed
expect(form.getFieldState("foo").dirty).toBe(true);
expect(form.getFieldState("foo").touched).toBe(true);
expect(form.getFieldState("foo").modified).toBe(true);

expect(form.ignoreUnregister).toBe(false);
form.setConfig("destroyOnUnregister", true);

// unregister should remove state
unregister();

// field state is preserved
expect(form.getFieldState("foo")).not.toBe(undefined);
expect(form.getFieldState("foo").dirty).toBe(true);
expect(form.getFieldState("foo").touched).toBe(true);
expect(form.getFieldState("foo").modified).toBe(true);

// re-register the same field
unregister = form.registerField("foo", subscriber, { value: true });

// dirty state is preserved, because FF doesn't touch the value on unregister
expect(form.getFieldState("foo").dirty).toBe(true);

// however the other states are reset
expect(form.getFieldState("foo").touched).toBe(true);
expect(form.getFieldState("foo").modified).toBe(true);

expect(form.ignoreUnregister).toBe(true);
});

it("should update ignoreUnregister on ignoreUnregister setter", () => {
const form = createForm({
onSubmit: onSubmitMock,
ignoreUnregister: true,
});

const subscriber = jest.fn();
let unregister = form.registerField("foo", subscriber, { value: true });

// initial state
expect(form.getFieldState("foo").dirty).toBe(false);
expect(form.getFieldState("foo").touched).toBe(false);
expect(form.getFieldState("foo").modified).toBe(false);

// change value
form.change("foo", "bar");
form.blur("foo");

// state changed
expect(form.getFieldState("foo").dirty).toBe(true);
expect(form.getFieldState("foo").touched).toBe(true);
expect(form.getFieldState("foo").modified).toBe(true);

expect(form.ignoreUnregister).toBe(false);
form.ignoreUnregister = true;

// unregister should remove state
unregister();

// field state is preserved
expect(form.getFieldState("foo")).not.toBe(undefined);
expect(form.getFieldState("foo").dirty).toBe(true);
expect(form.getFieldState("foo").touched).toBe(true);
expect(form.getFieldState("foo").modified).toBe(true);

// re-register the same field
unregister = form.registerField("foo", subscriber, { value: true });

// dirty state is preserved, because FF doesn't touch the value on unregister
expect(form.getFieldState("foo").dirty).toBe(true);

// however the other states are reset
expect(form.getFieldState("foo").touched).toBe(true);
expect(form.getFieldState("foo").modified).toBe(true);

expect(form.ignoreUnregister).toBe(true);
});
});
2 changes: 2 additions & 0 deletions src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ export interface FormApi<
blur: (name: keyof FormValues) => void;
change: <F extends keyof FormValues>(name: F, value?: FormValues[F]) => void;
destroyOnUnregister: boolean;
ignoreUnregister: boolean;
focus: (name: keyof FormValues) => void;
initialize: (
data: InitialFormValues | ((values: FormValues) => InitialFormValues),
Expand Down Expand Up @@ -304,6 +305,7 @@ export interface Config<
> {
debug?: DebugFunction<FormValues, InitialFormValues>;
destroyOnUnregister?: boolean;
ignoreUnregister?: boolean;
initialValues?: InitialFormValues;
keepDirtyOnReinitialize?: boolean;
mutators?: { [key: string]: Mutator<FormValues, InitialFormValues> };
Expand Down
3 changes: 3 additions & 0 deletions src/types.js.flow
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ export type InternalFormState<FormValues: FormValuesShape> = {
export type ConfigKey =
| "debug"
| "destroyOnUnregister"
| "ignoreUnregister"
| "initialValues"
| "keepDirtyOnReinitialize"
| "mutators"
Expand All @@ -216,6 +217,7 @@ export type FormApi<FormValues: FormValuesShape> = {
blur: (name: string) => void,
change: (name: string, value: ?any) => void,
destroyOnUnregister: boolean,
ignoreUnregister: boolean,
focus: (name: string) => void,
initialize: (data: Object | ((values: Object) => Object)) => void,
isValidationPaused: () => boolean,
Expand Down Expand Up @@ -286,6 +288,7 @@ export type Mutator<FormValues: FormValuesShape> = (
export type Config<FormValues: FormValuesShape> = {
debug?: DebugFunction<FormValues>,
destroyOnUnregister?: boolean,
ignoreUnregister?: boolean,
initialValues?: FormValues,
keepDirtyOnReinitialize?: boolean,
mutators?: { [string]: Mutator<FormValues> },
Expand Down

0 comments on commit 15d5e7d

Please sign in to comment.