@danderson00/react-forms 0.0.47 → 0.0.48

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/input.js CHANGED
@@ -1,173 +1,175 @@
1
- import { useCallback, useState, useEffect } from 'react'
2
- import { get } from 'object-path-immutable'
3
- import usePrevious from './usePrevious'
4
- import useUpdate from './useUpdate'
5
- import types from './inputTypes'
6
- import createThrottled from './throttle'
7
-
8
- const nextElementId = (id => () => `react-forms-${++id}`)(0)
9
-
10
- export default (render, options = {}) => function Input(props) {
11
- if (!props.name) throw new Error("You must provide a name prop to form components")
12
- if (!props.setFieldValue) throw new Error("Input components must be contained within a Form component")
13
-
14
- const [fieldValidated, setFieldValidated] = useState(false)
15
- const [error, setError] = useState(false)
16
- const [id] = useState(props.id || nextElementId())
17
- const previousValueProp = usePrevious(props.value)
18
-
19
- // this is currently broken :-\
20
- const { groups = [], throttle, onSubmit } = props
21
- const name = groups.map(group => group + '.') + props.name
22
- const submitHandler = (throttle && onSubmit) ? createThrottled(onSubmit, isNaN(throttle) ? 200 : throttle) : onSubmit
23
-
24
- useEffect(() => {
25
- const { getFieldValue, setFieldValue, registerFieldValidator } = props
26
- const { setInitialValue, applyValueTransforms } = types(props, options)
27
-
28
- // don't override values that were set from passing a values prop to the form
29
- if(getFieldValue(name) === undefined) {
30
- // TODO: change setter to a getter and return a private `UNSET` symbol to indicate not to call the field setter
31
- // this allows us to set the default value with useState above and not have an initial render cycle without the default value
32
- // BUT... to get the default value of a select list, we need to render it first? dilemma....
33
-
34
- setInitialValue({
35
- // pass a setter function - radio buttons do not always want to set a value!
36
- set: value => setFieldValue(name, applyValueTransforms(value)),
37
- element: document.querySelector(`#${id}`)
38
- })
39
- }
40
- registerFieldValidator(updateValidationState)
41
-
42
- // TODO: need to unregister in case of dynamic form elements
43
- }, [])
44
-
45
- // ensure form values are updated to reflect passed `value` prop
46
- // this probably introduces some redundancy with the onChange handler below
47
- useUpdate(() => {
48
- const { value, setFieldValue } = props
49
- const { applyValueTransforms } = types(props, options)
50
-
51
- if(value !== previousValueProp) {
52
- setFieldValue(name, applyValueTransforms(value))
53
- }
54
- }, [props])
55
-
56
- const updateValidationState = useCallback((newValue, oldValues) => {
57
- const { getValidationMessage, getFieldValue, getFieldValues } = props
58
- const element = document.querySelector(`#${id}`)
59
- if(element) {
60
- const validationMessage = getValidationMessage && getValidationMessage(
61
- newValue === undefined ? (getFieldValue(name) || get(oldValues, name)) : newValue,
62
- oldValues || getFieldValues()
63
- )
64
- element.setCustomValidity?.(validationMessage || "")
65
-
66
- setFieldValidated(true)
67
- // this is a little hacky, but the simplest way I could think of to determine the
68
- // validity of the actual input element. :invalid is also not supported by enzyme
69
- const invalid = !!validationMessage || Boolean(document.querySelector(`#${id}:invalid`))
70
- setError(invalid)
71
- props.onValidated && props.onValidated(!invalid)
72
- }
73
- }, [props])
74
-
75
- // TODO: refactor bubbling to prop pattern
76
- const onChange = useCallback((...args) => {
77
- const [event] = args
78
- const { setFieldValue, onChange, submitOnChange } = props
79
- const { valueFromEvent } = options
80
- const { getOutputValue, applyValueTransforms } = types(props, options)
81
-
82
- const elementValue = valueFromEvent
83
- ? valueFromEvent.apply(null, args)
84
- : getOutputValue({ event })
85
-
86
- setFieldValue(name, applyValueTransforms(elementValue))
87
-
88
- if(event && event.target && event.target.checkValidity) {
89
- event.target.checkValidity()
90
- updateValidationState(applyValueTransforms(elementValue))
91
- }
92
-
93
- if(onChange) {
94
- onChange.apply(event.target, args)
95
- }
96
-
97
- if(submitOnChange && submitHandler) {
98
- // the setFieldValue above sets state on the Form component, used in the onSubmit handler
99
- // setting state requires an additional render cycle - we need to pass in the new value so it's set correctly
100
- submitHandler(undefined, { additionalValues: { [name]: applyValueTransforms(elementValue) } })
101
- }
102
- }, [props.setFieldValue, props.onChange, props.submitOnChange, props.setFieldValue, updateValidationState])
103
-
104
- const onBlur = (...args) => {
105
- const { submitOnBlur, onSubmit, onBlur } = props
106
- const [event] = args
107
-
108
- if(submitOnBlur) {
109
- onSubmit()
110
- }
111
- if(onBlur) {
112
- onBlur.apply(event.target, args)
113
- }
114
- }
115
-
116
- const onFocus = (...args) => {
117
- const { selectOnFocus, onFocus } = props
118
- const [event] = args
119
- const element = event.target
120
-
121
- const selectText = () => {
122
- element.selectionStart = 0
123
- element.selectionEnd = element.value.length
124
- }
125
-
126
- if(selectOnFocus) {
127
- selectText()
128
- setTimeout(selectText)
129
- }
130
- if(onFocus) {
131
- onFocus.apply(element, args)
132
- }
133
- }
134
-
135
- const onKeyDown = (...args) => {
136
- const { onKeyDown } = props
137
- const [event] = args
138
- const element = event.target
139
-
140
- if(props.numeric && !/[0-9-]/.exec(event.key) && ![null, 0, 8, 9, 13, 27, 35, 36, 37, 39, 46].includes(event.keyCode)) {
141
- event.preventDefault()
142
- }
143
-
144
- if(onKeyDown) {
145
- onKeyDown.apply(element, args)
146
- }
147
- }
148
-
149
- /* render */ {
150
- const { passErrorProp } = options
151
- const { getValueProps } = types(props, options)
152
-
153
- const {
154
- type, value, checked, defaultValue, numeric, getValidationMessage,
155
- onSubmit, registerFieldValidator, setFieldValue, getFieldValue, getFieldValues,
156
- submitOnChange, submitOnBlur, selectOnFocus, throttle, valid, onValidated,
157
- ...passThroughProps
158
- } = props
159
- const valueProps = getValueProps({ currentValue: getFieldValue(name) })
160
- const numericProps = (numeric && { type: 'number' })
161
- const typeProps = (type && { type })
162
- const errorProps = (passErrorProp && { error })
163
- const className = ((fieldValidated ? 'validated ' : '') + (props.className || '') || undefined)
164
-
165
- const finalProps = {
166
- id, name, className,
167
- ...passThroughProps, ...valueProps, ...numericProps, ...typeProps, ...errorProps,
168
- onChange, onFocus, onBlur, onKeyDown
169
- }
170
-
171
- return render(finalProps)
172
- }
1
+ import { useCallback, useState, useEffect } from 'react'
2
+ import { get } from 'object-path-immutable'
3
+ import usePrevious from './usePrevious'
4
+ import useUpdate from './useUpdate'
5
+ import types from './inputTypes'
6
+ import createThrottled from './throttle'
7
+
8
+ const nextElementId = (id => () => `react-forms-${++id}`)(0)
9
+
10
+ export default (render, options = {}) => function Input(props) {
11
+ if (!props.name) throw new Error("You must provide a name prop to form components")
12
+ if (!props.setFieldValue) throw new Error("Input components must be contained within a Form component")
13
+
14
+ const [fieldValidated, setFieldValidated] = useState(false)
15
+ const [error, setError] = useState(false)
16
+ const [id] = useState(props.id || nextElementId())
17
+ const previousValueProp = usePrevious(props.value)
18
+
19
+ // this is currently broken :-\
20
+ const { groups = [], throttle, onSubmit } = props
21
+ const name = groups.map(group => group + '.') + props.name
22
+ const submitHandler = (throttle && onSubmit) ? createThrottled(onSubmit, isNaN(throttle) ? 200 : throttle) : onSubmit
23
+
24
+ useEffect(() => {
25
+ const { getFieldValue, setFieldValue, registerFieldValidator } = props
26
+ const { setInitialValue, applyValueTransforms } = types(props, options)
27
+
28
+ // don't override values that were set from passing a values prop to the form
29
+ if(getFieldValue(name) === undefined) {
30
+ // TODO: change setter to a getter and return a private `UNSET` symbol to indicate not to call the field setter
31
+ // this allows us to set the default value with useState above and not have an initial render cycle without the default value
32
+ // BUT... to get the default value of a select list, we need to render it first? dilemma....
33
+
34
+ setInitialValue({
35
+ // pass a setter function - radio buttons do not always want to set a value!
36
+ set: value => setFieldValue(name, applyValueTransforms(value)),
37
+ element: document.querySelector(`#${id}`)
38
+ })
39
+ }
40
+ registerFieldValidator(updateValidationState)
41
+
42
+ // TODO: need to unregister in case of dynamic form elements
43
+ }, [])
44
+
45
+ // ensure form values are updated to reflect passed `value` prop
46
+ // this probably introduces some redundancy with the onChange handler below
47
+ useUpdate(() => {
48
+ const { value, setFieldValue } = props
49
+ const { applyValueTransforms } = types(props, options)
50
+
51
+ if(value !== previousValueProp) {
52
+ setFieldValue(name, applyValueTransforms(value))
53
+ }
54
+ }, [props])
55
+
56
+ // when clicking submit, getFieldValue(s) return undefined due to an issue with closures I can't figure out
57
+ // the form component passes in its copy of field values as the oldValues parameter for this case
58
+ const updateValidationState = useCallback((newValue, oldValues) => {
59
+ const { getValidationMessage, getFieldValue, getFieldValues } = props
60
+ const element = document.querySelector(`#${id}`)
61
+ if(element) {
62
+ const validationMessage = getValidationMessage && getValidationMessage(
63
+ newValue === undefined ? (getFieldValue(name) || get(oldValues, name)) : newValue,
64
+ oldValues || getFieldValues()
65
+ )
66
+ element.setCustomValidity?.(validationMessage || "")
67
+
68
+ setFieldValidated(true)
69
+ // this is a little hacky, but the simplest way I could think of to determine the
70
+ // validity of the actual input element. :invalid is also not supported by enzyme
71
+ const invalid = !!validationMessage || Boolean(document.querySelector(`#${id}:invalid`))
72
+ setError(invalid)
73
+ props.onValidated && props.onValidated(!invalid)
74
+ }
75
+ }, [props])
76
+
77
+ // TODO: refactor bubbling to prop pattern
78
+ const onChange = useCallback((...args) => {
79
+ const [event] = args
80
+ const { setFieldValue, onChange, submitOnChange } = props
81
+ const { valueFromEvent } = options
82
+ const { getOutputValue, applyValueTransforms } = types(props, options)
83
+
84
+ const elementValue = valueFromEvent
85
+ ? valueFromEvent.apply(null, args)
86
+ : getOutputValue({ event })
87
+
88
+ setFieldValue(name, applyValueTransforms(elementValue))
89
+
90
+ if(event && event.target && event.target.checkValidity) {
91
+ event.target.checkValidity()
92
+ updateValidationState(applyValueTransforms(elementValue))
93
+ }
94
+
95
+ if(onChange) {
96
+ onChange.apply(event.target, args)
97
+ }
98
+
99
+ if(submitOnChange && submitHandler) {
100
+ // the setFieldValue above sets state on the Form component, used in the onSubmit handler
101
+ // setting state requires an additional render cycle - we need to pass in the new value so it's set correctly
102
+ submitHandler(undefined, { additionalValues: { [name]: applyValueTransforms(elementValue) } })
103
+ }
104
+ }, [props.setFieldValue, props.onChange, props.submitOnChange, props.setFieldValue, updateValidationState])
105
+
106
+ const onBlur = (...args) => {
107
+ const { submitOnBlur, onSubmit, onBlur } = props
108
+ const [event] = args
109
+
110
+ if(submitOnBlur) {
111
+ onSubmit()
112
+ }
113
+ if(onBlur) {
114
+ onBlur.apply(event.target, args)
115
+ }
116
+ }
117
+
118
+ const onFocus = (...args) => {
119
+ const { selectOnFocus, onFocus } = props
120
+ const [event] = args
121
+ const element = event.target
122
+
123
+ const selectText = () => {
124
+ element.selectionStart = 0
125
+ element.selectionEnd = element.value.length
126
+ }
127
+
128
+ if(selectOnFocus) {
129
+ selectText()
130
+ setTimeout(selectText)
131
+ }
132
+ if(onFocus) {
133
+ onFocus.apply(element, args)
134
+ }
135
+ }
136
+
137
+ const onKeyDown = (...args) => {
138
+ const { onKeyDown } = props
139
+ const [event] = args
140
+ const element = event.target
141
+
142
+ if(props.numeric && !/[0-9-]/.exec(event.key) && ![null, 0, 8, 9, 13, 27, 35, 36, 37, 39, 46].includes(event.keyCode)) {
143
+ event.preventDefault()
144
+ }
145
+
146
+ if(onKeyDown) {
147
+ onKeyDown.apply(element, args)
148
+ }
149
+ }
150
+
151
+ /* render */ {
152
+ const { passErrorProp } = options
153
+ const { getValueProps } = types(props, options)
154
+
155
+ const {
156
+ type, value, checked, defaultValue, numeric, getValidationMessage,
157
+ onSubmit, registerFieldValidator, setFieldValue, getFieldValue, getFieldValues,
158
+ submitOnChange, submitOnBlur, selectOnFocus, throttle, valid, onValidated,
159
+ ...passThroughProps
160
+ } = props
161
+ const valueProps = getValueProps({ currentValue: getFieldValue(name) })
162
+ const numericProps = (numeric && { type: 'number' })
163
+ const typeProps = (type && { type })
164
+ const errorProps = (passErrorProp && { error })
165
+ const className = ((fieldValidated ? 'validated ' : '') + (props.className || '') || undefined)
166
+
167
+ const finalProps = {
168
+ id, name, className,
169
+ ...passThroughProps, ...valueProps, ...numericProps, ...typeProps, ...errorProps,
170
+ onChange, onFocus, onBlur, onKeyDown
171
+ }
172
+
173
+ return render(finalProps)
174
+ }
173
175
  }
package/src/inputTypes.js CHANGED
@@ -1,63 +1,63 @@
1
- const resolveValue = (value, args) => typeof value === 'function' ? value.apply(null, args) : value
2
-
3
- export default (props, options) => {
4
- const { type, checked, value, defaultValue } = props
5
-
6
- const applyValueTransforms = value => ((props.type === 'number' || props.numeric) && !isNaN(+value) && value !== '') ? +value : value
7
-
8
- const defaultType = {
9
- setInitialValue: ({ set }) => {
10
- if(value !== undefined) {
11
- set(value)
12
- } else if (defaultValue !== undefined) {
13
- set(defaultValue)
14
- } else if (options.defaultValue !== undefined) {
15
- set(resolveValue(options.defaultValue, [props]))
16
- } else {
17
- set(undefined)
18
- }
19
- },
20
- getOutputValue: ({ event }) => {
21
- if (event.target && event.target.value !== undefined) {
22
- return event.target.value
23
- } else if (event.value !== undefined) {
24
- return event.value
25
- } else {
26
- throw new Error("Unable to determine input value from event")
27
- }
28
- },
29
- getValueProps: ({ currentValue }) => ({
30
- value: currentValue === undefined ? '' : currentValue
31
- })
32
- }
33
-
34
- const checkboxValue = (checked, value) => (value === undefined
35
- ? Boolean(checked)
36
- : checked ? value : undefined
37
- )
38
-
39
- const types = {
40
- text: defaultType,
41
- radio: {
42
- setInitialValue: ({ set }) => checked && set(value),
43
- getOutputValue: () => value,
44
- getValueProps: ({ currentValue }) => ({ checked: applyValueTransforms(value) === currentValue })
45
- },
46
- checkbox: {
47
- setInitialValue: ({ set }) => set(checkboxValue(checked, value)),
48
- getOutputValue: ({ event }) => checkboxValue(event.target.checked, value),
49
- getValueProps: ({ currentValue }) => ({ checked: Boolean(currentValue) })
50
- },
51
- select: {
52
- ...defaultType,
53
- setInitialValue: ({ set, element }) => {
54
- set(value || defaultValue ||
55
- (props.options && props.options.length > 0 && props.options[0]) || // this probably should come out - is it the right behavior for all browsers? it's here to keep 1 unit test happy?
56
- element && element.value || '' // this *should* work in all browsers (except jsdom)...
57
- )
58
- }
59
- }
60
- }
61
-
62
- return { ...(types[options.type || type] || types.text), applyValueTransforms }
63
- }
1
+ const resolveValue = (value, args) => typeof value === 'function' ? value.apply(null, args) : value
2
+
3
+ export default (props, options) => {
4
+ const { type, checked, value, defaultValue } = props
5
+
6
+ const applyValueTransforms = value => ((props.type === 'number' || props.numeric) && !isNaN(+value) && value !== '') ? +value : value
7
+
8
+ const defaultType = {
9
+ setInitialValue: ({ set }) => {
10
+ if(value !== undefined) {
11
+ set(value)
12
+ } else if (defaultValue !== undefined) {
13
+ set(defaultValue)
14
+ } else if (options.defaultValue !== undefined) {
15
+ set(resolveValue(options.defaultValue, [props]))
16
+ } else {
17
+ set(undefined)
18
+ }
19
+ },
20
+ getOutputValue: ({ event }) => {
21
+ if (event.target && event.target.value !== undefined) {
22
+ return event.target.value
23
+ } else if (event.value !== undefined) {
24
+ return event.value
25
+ } else {
26
+ throw new Error("Unable to determine input value from event")
27
+ }
28
+ },
29
+ getValueProps: ({ currentValue }) => ({
30
+ value: currentValue === undefined ? '' : currentValue
31
+ })
32
+ }
33
+
34
+ const checkboxValue = (checked, value) => (value === undefined
35
+ ? Boolean(checked)
36
+ : checked ? value : undefined
37
+ )
38
+
39
+ const types = {
40
+ text: defaultType,
41
+ radio: {
42
+ setInitialValue: ({ set }) => checked && set(value),
43
+ getOutputValue: () => value,
44
+ getValueProps: ({ currentValue }) => ({ checked: applyValueTransforms(value) === currentValue })
45
+ },
46
+ checkbox: {
47
+ setInitialValue: ({ set }) => set(checkboxValue(checked, value)),
48
+ getOutputValue: ({ event }) => checkboxValue(event.target.checked, value),
49
+ getValueProps: ({ currentValue }) => ({ checked: Boolean(currentValue) })
50
+ },
51
+ select: {
52
+ ...defaultType,
53
+ setInitialValue: ({ set, element }) => {
54
+ set(value || defaultValue ||
55
+ (props.options && props.options.length > 0 && props.options[0]) || // this probably should come out - is it the right behavior for all browsers? it's here to keep 1 unit test happy?
56
+ element && element.value || '' // this *should* work in all browsers (except jsdom)...
57
+ )
58
+ }
59
+ }
60
+ }
61
+
62
+ return { ...(types[options.type || type] || types.text), applyValueTransforms }
63
+ }
package/src/styles.js CHANGED
@@ -1,20 +1,20 @@
1
- import injectCss from './injectCss'
2
-
3
- injectCss('react-forms-styles', `
4
- .react-forms {
5
- display: flex;
6
- flex-direction: column;
7
- align-items: flex-start;
8
- }
9
-
10
- .react-forms-row {
11
- flex-direction: row;
12
- align-items: flex-start;
13
- }
14
-
15
- .react-forms-error {
16
- color: #f44336;
17
- }
18
- `)
19
-
20
-
1
+ import injectCss from './injectCss'
2
+
3
+ injectCss('react-forms-styles', `
4
+ .react-forms {
5
+ display: flex;
6
+ flex-direction: column;
7
+ align-items: flex-start;
8
+ }
9
+
10
+ .react-forms-row {
11
+ flex-direction: row;
12
+ align-items: flex-start;
13
+ }
14
+
15
+ .react-forms-error {
16
+ color: #f44336;
17
+ }
18
+ `)
19
+
20
+
package/src/submit.js CHANGED
@@ -1,29 +1,29 @@
1
- import React, { useState } from 'react'
2
-
3
- export default render => function Submit(props) {
4
- if(!props.onSubmit) throw new Error('Button components must be contained within a Form component')
5
-
6
- const {
7
- onSubmit, registerFieldValidator, setFieldValue, getFieldValue, getFieldValues,
8
- valid, validate, disableInvalid, values, ...propsToPass
9
- } = props
10
-
11
- // TODO: set executing when form is triggered by enter key
12
- const [executing, setExecuting] = useState(false)
13
-
14
- const onClick = e => {
15
- const result = onSubmit(e, { validate, additionalValues: values })
16
- if(result && result.finally && typeof result.finally === 'function') {
17
- setExecuting(true)
18
- result.finally(() => setExecuting(false))
19
- }
20
- }
21
-
22
- const finalProps = {
23
- ...propsToPass,
24
- onClick,
25
- disabled: props.disabled || executing || (disableInvalid && !valid)
26
- }
27
-
28
- return render(finalProps)
29
- }
1
+ import React, { useState } from 'react'
2
+
3
+ export default render => function Submit(props) {
4
+ if(!props.onSubmit) throw new Error('Button components must be contained within a Form component')
5
+
6
+ const {
7
+ onSubmit, registerFieldValidator, setFieldValue, getFieldValue, getFieldValues,
8
+ valid, validate, disableInvalid, values, ...propsToPass
9
+ } = props
10
+
11
+ // TODO: set executing when form is triggered by enter key
12
+ const [executing, setExecuting] = useState(false)
13
+
14
+ const onClick = e => {
15
+ const result = onSubmit(e, { validate, additionalValues: values })
16
+ if(result && result.finally && typeof result.finally === 'function') {
17
+ setExecuting(true)
18
+ result.finally(() => setExecuting(false))
19
+ }
20
+ }
21
+
22
+ const finalProps = {
23
+ ...propsToPass,
24
+ onClick,
25
+ disabled: props.disabled || executing || (disableInvalid && !valid)
26
+ }
27
+
28
+ return render(finalProps)
29
+ }
package/src/throttle.js CHANGED
@@ -1,11 +1,11 @@
1
- export default (target, delay = 200) => {
2
- let timeout
3
-
4
- return (...args) => {
5
- clearTimeout(timeout)
6
- timeout = setTimeout(
7
- () => target(...args),
8
- delay
9
- )
10
- }
1
+ export default (target, delay = 200) => {
2
+ let timeout
3
+
4
+ return (...args) => {
5
+ clearTimeout(timeout)
6
+ timeout = setTimeout(
7
+ () => target(...args),
8
+ delay
9
+ )
10
+ }
11
11
  }
package/src/useForm.js CHANGED
@@ -1,10 +1,11 @@
1
- import { useContext } from 'react'
2
-
3
- export default context => function useForm() {
4
- const { onSubmit, getFieldValues } = useContext(context)
5
-
6
- return {
7
- submit: options => onSubmit(undefined, options),
8
- values: () => getFieldValues()
9
- }
1
+ import { useContext } from 'react'
2
+
3
+ export default context => function useForm() {
4
+ const { onSubmit, getFieldValues, setFieldValue } = useContext(context)
5
+
6
+ return {
7
+ submit: options => onSubmit(undefined, options),
8
+ values: () => getFieldValues(),
9
+ setFieldValue
10
+ }
10
11
  }
@@ -1,5 +1,5 @@
1
- import { useContext } from 'react'
2
-
3
- export default context => function useFormValues() {
4
- return useContext(context).getFieldValues()
1
+ import { useContext } from 'react'
2
+
3
+ export default context => function useFormValues() {
4
+ return useContext(context).getFieldValues()
5
5
  }
@@ -1,9 +1,9 @@
1
- import { useRef, useEffect } from 'react'
2
-
3
- export default function usePrevious(value) {
4
- const ref = useRef()
5
- useEffect(() => {
6
- ref.current = value
7
- })
8
- return ref.current
1
+ import { useRef, useEffect } from 'react'
2
+
3
+ export default function usePrevious(value) {
4
+ const ref = useRef()
5
+ useEffect(() => {
6
+ ref.current = value
7
+ })
8
+ return ref.current
9
9
  }