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 })),
38 // track focused states
39 const [focused, _setFocused] = useState(() =>
40 defaultsFor(configs, config => config.autoFocus && !config.disabled)
42 const focuses = useMemo(
43 () => configs.map(config => focused[config.name] || false), //
47 const setFocused = useCallback(
48 (name, value) => _setFocused(values => ({ ...values, [name]: value })),
52 // track whether or not input has been focused
53 const [hasBeenFocused, _setHasBeenFocused] = useState({});
54 const setHasBeenFocused = useCallback(
56 _setHasBeenFocused(values => ({ ...values, [name]: value })),
60 const transform = useCallback(
61 (name, value) => compose(...get(byName, [name, 'transformers'], []))(value),
64 const validate = useCallback(
67 ...get(byName, [name, 'validators'], []),
73 const onChange = useCallback(
74 name => e => setValue(name, transform(name, e.target.value)), //
77 const onFocus = useCallback(
78 name => e => setFocused(name, true), //
81 const onBlur = useCallback(
83 setFocused(name, false);
84 setHasBeenFocused(name, true);
86 [setFocused, setHasBeenFocused]
89 // memo-compute whether or not the current value is valid
90 const validations = useMemo(
91 () => configs.map(config => validate(config.name, values[config.name])), //
92 [configs, validate, values]
95 const datas = useMemo(() => validations.map(v => v.data), [validations]);
96 const passes = useMemo(() => validations.map(v => v.pass), [validations]);
98 // the input has errored if it
99 // 1) did not pass validation
100 // 2) has an error text
101 // 3) has been focused before
102 // 4) is not currently focused
103 // returning the error text at the end
104 const errors = useMemo(
108 !validations[i].pass &&
109 hasBeenFocused[config.name] &&
110 !focused[config.name] &&
113 [configs, validations, hasBeenFocused, focused]
116 // visibly tell the user that their input has passed if
118 // 2) they are or have interacted with the input before
119 const visiblePasses = useMemo(
123 validations[i].pass &&
124 (hasBeenFocused[config.name] || focused[config.name])
126 [configs, validations, hasBeenFocused, focused]
129 const inputs = useMemo(
131 configs.map((config, i) => ({
135 visiblyPassed: visiblePasses[i],
139 autoFocus: config.autoFocus && !config.disabled,
140 // dom properties below:
142 value: values[config.name],
143 onChange: onChange(config.name),
144 onFocus: onFocus(config.name),
145 onBlur: onBlur(config.name),
146 autoFocus: config.autoFocus && !config.disabled,
163 const pass = every(passes);
164 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