on master help sign in
feat: implement hasbeentouched for inputs and integrate into error display
commite471cdbf13b8b2209209c82eefbbcba51c1cad57 authorMatt Condon (shrugs) <mattgcondon@gmail.com> dateMon 8 Jul 2019, 13:29 (7 years ago) parentb0440cf
4 files changed+48−19
src/indigo-react/components/CheckboxInput.js+3−2 src/indigo-react/components/Input.js+3−2 src/indigo-react/components/SelectInput.js+3−2 src/indigo-react/lib/useForm.js+39−13
src/indigo-react/components/CheckboxInput.js+3−2
@@ -19,6 +19,7 @@ export default function CheckboxInput({
1919 pass,
2020 visiblyPassed,
2121 error,
22+ hintError,
2223 data,
2324 bind,
2425 autoFocus,
@@ -59,8 +60,8 @@ export default function CheckboxInput({
5960 {
6061 'b-green3': visiblyPassed,
6162 'b-black': focused && !visiblyPassed,
62− 'b-yellow3': !focused && error,
63− 'b-gray3': !focused && !error && !visiblyPassed,
63+ 'b-yellow3': !focused && hintError,
64+ 'b-gray3': !focused && !hintError && !visiblyPassed,
6465 }
6566 )}
6667 id={name}
src/indigo-react/components/Input.js+3−2
@@ -21,6 +21,7 @@ export default function Input({
2121 pass,
2222 visiblyPassed,
2323 error,
24+ hintError,
2425 data,
2526 bind,
2627 autoFocus,
@@ -84,8 +85,8 @@ export default function Input({
8485 {
8586 'b-green3': visiblyPassed,
8687 'b-black': focused && !visiblyPassed,
87− 'b-yellow3': !focused && error,
88− 'b-gray3': !focused && !error && !visiblyPassed,
88+ 'b-yellow3': !focused && hintError,
89+ 'b-gray3': !focused && !hintError && !visiblyPassed,
8990 }
9091 // TODO: inputClassName ?
9192 )}
src/indigo-react/components/SelectInput.js+3−2
@@ -25,6 +25,7 @@ export default function SelectInput({
2525 pass,
2626 visiblyPassed,
2727 error,
28+ hintError,
2829 data,
2930 bind,
3031 autoFocus,
@@ -92,8 +93,8 @@ export default function SelectInput({
9293 {
9394 'b-green3': visiblyPassed,
9495 'b-black': focused && !visiblyPassed,
95− 'b-yellow3': !focused && error,
96− 'b-gray3': !focused && !error && !visiblyPassed,
96+ 'b-yellow3': !focused && hintError,
97+ 'b-gray3': !focused && !hintError && !visiblyPassed,
9798 }
9899 )}
99100 id={name}
src/indigo-react/lib/useForm.js+39−13
@@ -61,6 +61,9 @@ export default function useForm(inputConfigs = []) {
6161 // track whether or not input has been focused
6262 const [hasBeenFocused, setHasBeenFocused] = useSetState({});
6363
64+ // track whether or not the input has been touched
65+ const [hasBeenTouched, setHasBeenTouched] = useSetState({});
66+
6467 // build fn that transforms a value by input name
6568 const transform = useCallback(
6669 (name, value) => compose(...get(byName, [name, 'transformers'], []))(value),
@@ -97,8 +100,11 @@ export default function useForm(inputConfigs = []) {
97100
98101 // on change, transform and set value
99102 const onChange = useCallback(
100− name => e => setValue(name, getValue(name, e)), //
101− [setValue, getValue]
103+ name => e => {
104+ setValue(name, getValue(name, e));
105+ setHasBeenTouched({ [name]: true });
106+ }, //
107+ [setValue, getValue, setHasBeenTouched]
102108 );
103109
104110 // on focus, update focus
@@ -126,27 +132,45 @@ export default function useForm(inputConfigs = []) {
126132 const datas = useMemo(() => validations.map(v => v.data), [validations]);
127133
128134 // the input has errored if it
135+ // 1) did not pass validation and has an error text
136+ // 2) or has a specific error from parent state
137+ // and then we store that in errorTexts[i]
138+ // <string | falsy>[]
139+ const errorTexts = useMemo(
140+ () =>
141+ configs.map((config, i) => validations[i].error || config.error || false),
142+ [configs, validations]
143+ );
144+
145+ // we should hint at an error if it
129146 // 1) has been focused before
130− // 2)
131− // a) did not pass validation and has an error text
132− // b) or has a specific error from parent state
133− // (and we implicitly return the error text at the end)
134− const errors = useMemo(
147+ // 2) and has an error
148+ // boolean[]
149+ const hintErrors = useMemo(
135150 () =>
136151 configs.map(
137− (config, i) =>
138− hasBeenFocused[config.name] && //
139− (validations[i].error || config.error)
152+ (config, i) => hasBeenFocused[config.name] && !!errorTexts[i]
140153 ),
141− [configs, validations, hasBeenFocused]
154+ [configs, errorTexts, hasBeenFocused]
155+ );
156+
157+ // we should block on error (with potential reflow) if it
158+ // 1) has been touched
159+ // 2) and has an error
160+ // consumers should use this value to show error text and block on operations
161+ // <string | falsy>[]
162+ const errors = useMemo(
163+ () =>
164+ configs.map((config, i) => hasBeenTouched[config.name] && errorTexts[i]),
165+ [configs, errorTexts, hasBeenTouched]
142166 );
143167
144168 // this input has passed if
145169 // 1) it has passed local validation
146170 // 2) there are no errors
147171 const passes = useMemo(
148− () => validations.map((v, i) => v.pass && !errors[i]),
149− [validations, errors]
172+ () => validations.map((v, i) => v.pass && !errorTexts[i]),
173+ [validations, errorTexts]
150174 );
151175
152176 // visibly tell the user that their input has passed if
@@ -176,6 +200,7 @@ export default function useForm(inputConfigs = []) {
176200 pass: passes[i],
177201 visiblyPassed: visiblePasses[i],
178202 error: errors[i],
203+ hintError: hintErrors[i],
179204 focused: focuses[i],
180205 autoFocus: autoFocus && !disabled,
181206 disabled,
@@ -198,6 +223,7 @@ export default function useForm(inputConfigs = []) {
198223 visiblePasses,
199224 passes,
200225 errors,
226+ hintErrors,
201227 focuses,
202228 values,
203229 onBlur,