Skip to content

Commit a838684

Browse files
authored
Add ability to reset fields (#100)
1 parent 9dd59ed commit a838684

6 files changed

+105
-12
lines changed

README.md

+33-6
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
- [`formOptions.onChange`](#formoptionsonchange)
4848
- [`formOptions.onTouched`](#formoptionsontouched)
4949
- [`formOptions.onClear`](#formoptionsonclear)
50+
- [`formOptions.onReset`](#formoptionsonreset)
5051
- [`formOptions.withIds`](#formoptionswithids)
5152
- [`[formState, inputs]`](#formstate-inputs)
5253
- [Form State](#form-state)
@@ -128,6 +129,8 @@ From the example above, as the user fills in the form, the `formState` object wi
128129
},
129130
clear: Function,
130131
clearField: Function,
132+
reset: Function,
133+
resetField: Function,
131134
setField: Function,
132135
}
133136
```
@@ -385,21 +388,26 @@ function Form() {
385388

386389
Please note that when `formState.setField` is called, any existing errors that might have been set due to previous interactions from the user will be cleared, and both of the `validity` and the `touched` states of the input will be set to `true`.
387390

388-
It's also possible to set the error value for a single input using `formState.setFieldError` and to clear a single input's value using `formState.clearField`.
391+
It's also possible to clear a single input's value or to reset it to its initial value, if provided, using `formState.clearField` and `formState.resetField` respectively.
392+
393+
As a convenience you can also set the error value for a single input using `formState.setFieldError`.
389394

390395
### Resetting The From State
391396

392-
All fields in the form can be cleared all at once at any time using `formState.clear`.
397+
The form state can be cleared or reset back to its initial state if provided at any time using `formState.clear` and `formState.reset` respectively.
393398

394399
```js
395400
function Form() {
396-
const [formState, { text, email }] = useFormState();
401+
const [formState, { text, email }] = useFormState({
402+
403+
});
397404
return (
398405
<>
399-
<input {...text("first_name")} />
400-
<input {...text("last_name")} />
401-
<input {...email("email")} />
406+
<input {...text('first_name')} />
407+
<input {...text('last_name')} />
408+
<input {...email('email')} />
402409
<button onClick={formState.clear}>Clear All Fields</button>
410+
<button onClick={formState.reset}>Reset to Initial State</button>
403411
</>
404412
);
405413
}
@@ -531,6 +539,19 @@ const [formState, inputs] = useFormState(null, {
531539
formState.clear(); // clearing the form state
532540
```
533541

542+
#### `formOptions.onReset`
543+
544+
A function that gets called after calling `formState.reset` indicating that all fields in the form state are set to their initial values.
545+
546+
```js
547+
const [formState, inputs] = useFormState(null, {
548+
onReset() {
549+
// form state was reset successfully
550+
}
551+
});
552+
formState.reset(); // resetting the form state
553+
```
554+
534555
#### `formOptions.withIds`
535556

536557
Indicates whether `useFormState` should generate and pass an `id` attribute to its fields. This is helpful when [working with labels](#labels-and-ids).
@@ -598,6 +619,12 @@ formState = {
598619
// clears the state of an input
599620
clearField(name: string): void,
600621

622+
// resets all fields the form back to their initial state if provided
623+
reset(): void,
624+
625+
// resets the state of an input back to its initial state if provided
626+
resetField(name: string): void,
627+
601628
// updates the value of an input
602629
setField(name: string, value: string): void,
603630

src/index.d.ts

+3
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,12 @@ interface FormState<T, E = StateErrors<T, string>> {
2323
validity: StateValidity<T>;
2424
touched: StateValidity<T>;
2525
errors: E;
26+
reset(): void;
2627
clear(): void;
2728
setField<K extends keyof T>(name: K, value: T[K]): void;
2829
setFieldError(name: keyof T, error: string): void;
2930
clearField(name: keyof T): void;
31+
resetField(name: keyof T): void;
3032
}
3133

3234
interface FormOptions<T> {
@@ -37,6 +39,7 @@ interface FormOptions<T> {
3739
): void;
3840
onBlur(event: React.FocusEvent<InputElement>): void;
3941
onClear(): void;
42+
onReset(): void;
4043
onTouched(event: React.FocusEvent<InputElement>): void;
4144
withIds: boolean | ((name: string, value?: string) => string);
4245
}

src/useFormState.js

+9-5
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,11 @@ import {
1818
} from './constants';
1919

2020
const defaultFormOptions = {
21-
onChange: noop,
2221
onBlur: noop,
23-
onTouched: noop,
22+
onChange: noop,
2423
onClear: noop,
24+
onReset: noop,
25+
onTouched: noop,
2526
withIds: false,
2627
};
2728

@@ -59,7 +60,7 @@ export default function useFormState(initialState, options) {
5960
// probably fine.
6061
const key = `${type}.${name}.${toString(ownValue)}`;
6162

62-
function setInitialValue() {
63+
function setDefaultValue() {
6364
/* istanbul ignore else */
6465
if (process.env.NODE_ENV === 'development') {
6566
if (isRaw && formState.current.values[name] === undefined) {
@@ -181,9 +182,12 @@ export default function useFormState(initialState, options) {
181182
}
182183
},
183184
get value() {
184-
// auto populating initial state values on first render
185185
if (!hasValueInState) {
186-
setInitialValue();
186+
// auto populating default values if an initial value is not provided
187+
setDefaultValue();
188+
} else if (!formState.initialValues.has(name)) {
189+
// keep track of user-provided initial values on first render
190+
formState.initialValues.set(name, formState.current.values[name]);
187191
}
188192

189193
// auto populating default values of touched

src/useState.js

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import { useReducer, useRef } from 'react';
22
import { isFunction } from './utils';
3+
import { useCache } from './useCache';
34

45
function stateReducer(state, newState) {
56
return isFunction(newState) ? newState(state) : { ...state, ...newState };
67
}
78

8-
export function useState({ initialState, onClear }) {
9+
export function useState({ initialState, onClear, onReset }) {
910
const state = useRef();
11+
const initialValues = useCache();
1012
const [values, setValues] = useReducer(stateReducer, initialState || {});
1113
const [touched, setTouched] = useReducer(stateReducer, {});
1214
const [validity, setValidity] = useReducer(stateReducer, {});
@@ -22,6 +24,7 @@ export function useState({ initialState, onClear }) {
2224
}
2325

2426
const clearField = name => setField(name);
27+
const resetField = name => setField(name, initialValues.get(name));
2528

2629
return {
2730
/**
@@ -34,12 +37,18 @@ export function useState({ initialState, onClear }) {
3437
setTouched,
3538
setValidity,
3639
setError,
40+
initialValues,
3741
controls: {
3842
clearField,
43+
resetField,
3944
clear() {
4045
Object.keys(state.current.values).forEach(clearField);
4146
onClear();
4247
},
48+
reset() {
49+
Object.keys(state.current.values).forEach(resetField);
50+
onReset();
51+
},
4352
setField(name, value) {
4453
setField(name, value, true, true);
4554
},

test/useFormState-manual-updates.test.js

+48
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,19 @@ describe('useFormState manual updates', () => {
1414
expect(formState.current.values.name).toEqual('');
1515
});
1616

17+
it('resets a field to its initial value on form.resetField', () => {
18+
const { formState, change } = renderWithFormState(
19+
([, input]) => <input {...input.text('name')} />,
20+
{ name: 'waseem' },
21+
);
22+
23+
change({ value: 'cool' });
24+
expect(formState.current.values.name).toEqual('cool');
25+
26+
formState.current.resetField('name');
27+
expect(formState.current.values.name).toEqual('waseem');
28+
});
29+
1730
it('clears the entire all form fields using form.clear', () => {
1831
const onClear = jest.fn();
1932
const { root, formState, change, click } = renderWithFormState(
@@ -47,6 +60,41 @@ describe('useFormState manual updates', () => {
4760
expect(onClear).toHaveBeenCalled();
4861
});
4962

63+
it('resets the entire all form fields to their initial values using form.reset', () => {
64+
const onReset = jest.fn();
65+
const initialState = {
66+
first: 'waseem',
67+
last: 'dahman',
68+
role: ['user'],
69+
};
70+
71+
const { root, formState, change, click } = renderWithFormState(
72+
([, input]) => (
73+
<div>
74+
<input {...input.text('first')} />
75+
<input {...input.text('last')} />
76+
<input {...input.checkbox('role', 'admin')} />
77+
<input {...input.checkbox('role', 'user')} />
78+
</div>
79+
),
80+
initialState,
81+
{ onReset },
82+
);
83+
84+
change({ value: 'bruce' }, root.childNodes[0]);
85+
change({ value: 'wayne' }, root.childNodes[1]);
86+
click({}, root.childNodes[3]);
87+
expect(formState.current.values).toEqual({
88+
first: 'bruce',
89+
last: 'wayne',
90+
role: [],
91+
});
92+
93+
formState.current.reset();
94+
expect(formState.current.values).toEqual(initialState);
95+
expect(onReset).toHaveBeenCalled();
96+
});
97+
5098
it('sets the value of an input programmatically using from.setField', () => {
5199
const { formState } = renderWithFormState(([, input]) => (
52100
<input {...input.text('name')} />

test/useFormState.test.js

+2
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@ describe('useFormState API', () => {
1111
touched: {},
1212
errors: {},
1313
clear: expect.any(Function),
14+
reset: expect.any(Function),
1415
setField: expect.any(Function),
1516
setFieldError: expect.any(Function),
1617
clearField: expect.any(Function),
18+
resetField: expect.any(Function),
1719
},
1820
expect.any(Object),
1921
]);

0 commit comments

Comments
 (0)