1import { useCallback, useMemo, useState } from 'react';
2import { keyBy, get, every, some } from 'lodash';
4import { compose } from 'lib/lib';
5import { kDefaultValidator } from 'lib/validators';
7// interface InputConfig {
9// validators: Function[];
10// transformers: Function[];
11// initialValue: string;
17const defaultsFor = (configs, mapper) =>
19 (memo, config) => ({ ...memo, [config.name]: mapper(config) }),
24 * useForm managest a set of inputs for rendering them in a loop
26export default function useForm(configs = []) {
27 const byName = useMemo(() => keyBy(configs, 'name'), [configs]);
30 const [values, _setValues] = useState(() =>
31 defaultsFor(configs, config => config.initialValue)
33 const setValue = useCallback(
34 (name, value) => _setValues(values => ({ ...values, [name]: value })),
37 // ^ NB(shrugs): because we're not syncing the initialValue of _additional_
38 // configs that are added, we won't be able to set an initialValue for a
39 // dynamic form that then triggers validation. for initial configs,
40 // initialValue can be a validated string and everything will be happy,
41 // but for dynamically added input configs, we won't get validation, etc
42 // until there's a traditional state update.
44 // track focused states
45 const [focused, _setFocused] = useState(() =>
46 defaultsFor(configs, config => config.autoFocus && !config.disabled)
48 const focuses = useMemo(
49 () => configs.map(config => focused[config.name] || false), //
53 const setFocused = useCallback(
54 (name, value) => _setFocused(values => ({ ...values, [name]: value })),
58 // track whether or not input has been focused
59 const [hasBeenFocused, _setHasBeenFocused] = useState({});
60 const setHasBeenFocused = useCallback(
62 _setHasBeenFocused(values => ({ ...values, [name]: value })),
66 const transform = useCallback(
67 (name, value) => compose(...get(byName, [name, 'transformers'], []))(value),
70 const validate = useCallback(
73 ...get(byName, [name, 'validators'], []),
79 const onChange = useCallback(
80 name => e => setValue(name, transform(name, e.target.value)), //
83 const onFocus = useCallback(
84 name => e => setFocused(name, true), //
87 const onBlur = useCallback(
89 setFocused(name, false);
90 setHasBeenFocused(name, true);
92 [setFocused, setHasBeenFocused]
95 // memo-compute whether or not the current value is valid
96 const validations = useMemo(
97 () => configs.map(config => validate(config.name, values[config.name])), //
98 [configs, validate, values]
101 const datas = useMemo(() => validations.map(v => v.data), [validations]);
103 // the input has errored if it
104 // 1) has been focused before
106 // a) did not pass validation and has an error text
107 // b) or has a specific error from parent state
108 // (and we implicitly return the error text at the end)
109 const errors = useMemo(
113 hasBeenFocused[config.name] && //
114 (validations[i].error || config.error)
116 [configs, validations, hasBeenFocused]
119 // this input has passed if
120 // 1) it has passed local validation
121 // 2) there are no errors
122 const passes = useMemo(
123 () => validations.map((v, i) => v.pass && !errors[i]),
124 [validations, errors]
127 // visibly tell the user that their input has passed if
128 // 1) they are or have interacted with the input before
129 // 2) and it has passed as defined above
130 const visiblePasses = useMemo(
134 (focused[config.name] || hasBeenFocused[config.name]) && //
137 [configs, hasBeenFocused, focused, passes]
140 const inputs = useMemo(
143 ({ name, error, autoFocus, disabled, initialValue, ...rest }, i) => ({
148 visiblyPassed: visiblePasses[i],
151 autoFocus: autoFocus && !disabled,
154 // dom properties below:
156 value: values[name] || initialValue,
157 onChange: onChange(name),
158 onFocus: onFocus(name),
159 onBlur: onBlur(name),
160 autoFocus: autoFocus && !disabled,
178 const pass = every(passes);
179 const error = some(errors);
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list