@@ -3,66 +3,99 @@ import stateReducer from './stateReducer';
3
3
import { TYPES , SELECT , CHECKBOX , RADIO } from './constants' ;
4
4
5
5
export default function useFormState ( initialState ) {
6
- const [ values , setFormState ] = useReducer ( stateReducer , initialState || { } ) ;
6
+ const [ state , setState ] = useReducer ( stateReducer , initialState || { } ) ;
7
7
const [ touched , setTouchedState ] = useReducer ( stateReducer , { } ) ;
8
8
const [ validity , setValidityState ] = useReducer ( stateReducer , { } ) ;
9
9
10
- const createPropsGetter = type => ( name , value ) => {
11
- const hasValue = values [ name ] !== undefined ;
10
+ const createPropsGetter = type => ( name , ownValue ) => {
11
+ const hasOwnValue = ! ! ownValue ;
12
+ const hasValueInState = state [ name ] !== undefined ;
13
+ const isCheckbox = type === CHECKBOX ;
14
+ const isRadio = type === RADIO ;
15
+
16
+ function setInitialValue ( ) {
17
+ let value = '' ;
18
+ if ( isCheckbox ) {
19
+ /**
20
+ * If a checkbox has a user-defined value, its value the form state
21
+ * value will be an array. Otherwise it will be considered a toggle.
22
+ */
23
+ value = hasOwnValue ? [ ] : false ;
24
+ }
25
+ setState ( { [ name ] : value } ) ;
26
+ }
27
+
28
+ function getNextCheckboxValue ( e ) {
29
+ const { value, checked } = e . target ;
30
+ if ( ! hasOwnValue ) {
31
+ return checked ;
32
+ }
33
+ const checkedValues = new Set ( state [ name ] ) ;
34
+ if ( checked ) {
35
+ checkedValues . add ( value ) ;
36
+ } else {
37
+ checkedValues . delete ( value ) ;
38
+ }
39
+ return Array . from ( checkedValues ) ;
40
+ }
41
+
12
42
const inputProps = {
13
43
name,
14
44
get type ( ) {
15
- if ( type !== SELECT ) {
16
- return type ;
17
- }
45
+ if ( type !== SELECT ) return type ;
18
46
} ,
19
47
get checked ( ) {
20
- if ( type === CHECKBOX ) {
21
- return hasValue ? values [ name ] . includes ( value ) : false ;
48
+ if ( isRadio ) {
49
+ return state [ name ] === ownValue ;
22
50
}
23
- if ( type === RADIO ) {
24
- return values [ name ] === value ;
51
+ if ( isCheckbox ) {
52
+ if ( ! hasOwnValue ) {
53
+ return state [ name ] || false ;
54
+ }
55
+ /**
56
+ * @todo Handle the case where two checkbox inputs share the same
57
+ * name, but one has a value, the other doesn't (throws currently).
58
+ * <input {...input.checkbox('option1')} />
59
+ * <input {...input.checkbox('option1', 'value_of_option1')} />
60
+ */
61
+ return hasValueInState ? state [ name ] . includes ( ownValue ) : false ;
25
62
}
26
63
} ,
27
64
get value ( ) {
28
- // populating values of the form state on first render
29
- if ( ! hasValue ) {
30
- setFormState ( { [ name ] : type === CHECKBOX ? [ ] : '' } ) ;
31
- }
32
- if ( type === CHECKBOX || type === RADIO ) {
33
- return value ;
65
+ // auto populating initial state values on first render
66
+ if ( ! hasValueInState ) {
67
+ setInitialValue ( ) ;
34
68
}
35
- if ( hasValue ) {
36
- return values [ name ] ;
69
+ /**
70
+ * Since checkbox and radio inputs have their own user-defined values,
71
+ * and since checkbox inputs can be either an array or a boolean,
72
+ * returning the value of input from the current form state is illogical
73
+ */
74
+ if ( isCheckbox || isRadio ) {
75
+ return ownValue ;
37
76
}
38
- return '' ;
77
+ return hasValueInState ? state [ name ] : '' ;
39
78
} ,
40
79
onChange ( e ) {
41
- const { value : targetValue , checked } = e . target ;
42
- let inputValue = targetValue ;
43
- if ( type === CHECKBOX ) {
44
- const checkedValues = new Set ( values [ name ] ) ;
45
- if ( checked ) {
46
- checkedValues . add ( inputValue ) ;
47
- } else {
48
- checkedValues . delete ( inputValue ) ;
49
- }
50
- inputValue = Array . from ( checkedValues ) ;
80
+ let { value } = e . target ;
81
+ if ( isCheckbox ) {
82
+ value = getNextCheckboxValue ( e ) ;
51
83
}
52
- setFormState ( { [ name ] : inputValue } ) ;
84
+ setState ( { [ name ] : value } ) ;
53
85
} ,
54
86
onBlur ( e ) {
55
87
setTouchedState ( { [ name ] : true } ) ;
56
88
setValidityState ( { [ name ] : e . target . validity . valid } ) ;
57
89
} ,
58
90
} ;
91
+
59
92
return inputProps ;
60
93
} ;
61
94
62
- const typeMethods = TYPES . reduce (
95
+ const inputPropsCreators = TYPES . reduce (
63
96
( methods , type ) => ( { ...methods , [ type ] : createPropsGetter ( type ) } ) ,
64
97
{ } ,
65
98
) ;
66
99
67
- return [ { values, touched , validity } , typeMethods ] ;
100
+ return [ { values : state , validity, touched } , inputPropsCreators ] ;
68
101
}
0 commit comments