Skip to content

Commit 8df699f

Browse files
authored
Persist reference to formstate (#116)
* Use persistent reference to formstate * clean up code * Add test
1 parent f19100d commit 8df699f

File tree

4 files changed

+59
-43
lines changed

4 files changed

+59
-43
lines changed

src/constants.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export const LABEL = 'label';
2424
*/
2525
export const DATETIME_LOCAL = 'datetime-local';
2626

27-
export const TYPES = [
27+
export const INPUT_TYPES = [
2828
CHECKBOX,
2929
COLOR,
3030
DATE,

src/useFormState.js

+36-15
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1+
import { useRef } from 'react';
12
import { toString, noop, omit, isFunction, isEmpty } from './utils';
23
import { parseInputArgs } from './parseInputArgs';
34
import { useInputId } from './useInputId';
45
import { useCache } from './useCache';
56
import { useState } from './useState';
67
import {
7-
TYPES,
8+
INPUT_TYPES,
89
SELECT,
910
CHECKBOX,
1011
RADIO,
@@ -29,7 +30,7 @@ const defaultFormOptions = {
2930
export default function useFormState(initialState, options) {
3031
const formOptions = { ...defaultFormOptions, ...options };
3132

32-
const formState = useState({ initialState, ...formOptions });
33+
const formState = useState({ initialState });
3334
const { getIdProp } = useInputId(formOptions.withIds);
3435
const { set: setDirty, has: isDirty } = useCache();
3536
const callbacks = useCache();
@@ -290,19 +291,39 @@ export default function useFormState(initialState, options) {
290291
: inputProps;
291292
};
292293

293-
const inputPropsCreators = TYPES.reduce(
294-
(methods, type) => ({ ...methods, [type]: createPropsGetter(type) }),
295-
{},
296-
);
297-
298-
return [
299-
{
300-
...formState.current,
301-
...formState.controls,
294+
const formStateAPI = useRef({
295+
clearField: formState.clearField,
296+
resetField: formState.resetField,
297+
setField(name, value) {
298+
formState.setField(name, value, true, true);
299+
},
300+
setFieldError(name, error) {
301+
formState.setValidity({ [name]: false });
302+
formState.setError({ [name]: error });
303+
},
304+
clear() {
305+
formState.forEach(formState.clearField);
306+
formOptions.onClear();
302307
},
303-
{
304-
...inputPropsCreators,
305-
[LABEL]: (name, ownValue) => getIdProp('htmlFor', name, ownValue),
308+
reset() {
309+
formState.forEach(formState.resetField);
310+
formOptions.onReset();
306311
},
307-
];
312+
});
313+
314+
// exposing current form state (e.g. values, touched, validity, etc)
315+
// eslint-disable-next-line guard-for-in, no-restricted-syntax
316+
for (const key in formState.current) {
317+
formStateAPI.current[key] = formState.current[key];
318+
}
319+
320+
const inputPropsCreators = {
321+
[LABEL]: (name, ownValue) => getIdProp('htmlFor', name, ownValue),
322+
};
323+
324+
INPUT_TYPES.forEach(type => {
325+
inputPropsCreators[type] = createPropsGetter(type);
326+
});
327+
328+
return [formStateAPI.current, inputPropsCreators];
308329
}

src/useState.js

+13-27
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ function stateReducer(state, newState) {
66
return isFunction(newState) ? newState(state) : { ...state, ...newState };
77
}
88

9-
export function useState({ initialState, onClear, onReset }) {
9+
export function useState({ initialState }) {
1010
const state = useRef();
1111
const initialValues = useCache();
1212
const [values, setValues] = useReducer(stateReducer, initialState || {});
@@ -23,47 +23,33 @@ export function useState({ initialState, onClear, onReset }) {
2323
setError({ [name]: inputError });
2424
}
2525

26-
const clearField = name => {
26+
function clearField(name) {
2727
setField(name);
28-
};
28+
}
2929

30-
const resetField = name => {
30+
function resetField(name) {
3131
setField(
3232
name,
3333
initialValues.has(name) ? initialValues.get(name) : initialState[name],
3434
);
35-
};
35+
}
36+
37+
function forEach(cb) {
38+
Object.keys(state.current.values).forEach(cb);
39+
}
3640

3741
return {
38-
/**
39-
* @type {{ values, touched, validity, errors }}
40-
*/
4142
get current() {
4243
return state.current;
4344
},
4445
setValues,
4546
setTouched,
4647
setValidity,
4748
setError,
49+
setField,
4850
initialValues,
49-
controls: {
50-
clearField,
51-
resetField,
52-
clear() {
53-
Object.keys(state.current.values).forEach(clearField);
54-
onClear();
55-
},
56-
reset() {
57-
Object.keys(state.current.values).forEach(resetField);
58-
onReset();
59-
},
60-
setField(name, value) {
61-
setField(name, value, true, true);
62-
},
63-
setFieldError(name, error) {
64-
setValidity({ [name]: false });
65-
setError({ [name]: error });
66-
},
67-
},
51+
resetField,
52+
clearField,
53+
forEach,
6854
};
6955
}

test/useFormState.test.js

+9
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,13 @@ describe('useFormState API', () => {
4848
const [formState] = result.current;
4949
expect(formState.values).toEqual(expect.objectContaining(initialState));
5050
});
51+
52+
it('persists reference to the formState object', () => {
53+
const firstRenderResult = { current: null };
54+
const { result, rerender } = renderHook(() => useFormState());
55+
[firstRenderResult.current] = result.current;
56+
rerender();
57+
expect(result.current[0]).toBe(firstRenderResult.current);
58+
expect(result.current[0].setField).toBe(firstRenderResult.current.setField);
59+
});
5160
});

0 commit comments

Comments
 (0)