Skip to content

Commit 057c7a2

Browse files
committed
fix: stabilize useField public handler identity
BREAKING: onBlur, onChange, onFocus handlers no longer change identity if props or internal state is changed, thus on consumer side the behavior relying on handler identity can break Closes #816
1 parent 2d9e712 commit 057c7a2

File tree

2 files changed

+347
-35
lines changed

2 files changed

+347
-35
lines changed

src/useField.js

Lines changed: 59 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -130,24 +130,68 @@ function useField<FormValues: FormValuesShape>(
130130
]
131131
)
132132

133+
const _valueRef = React.useRef(_value)
134+
React.useEffect(() => {
135+
_valueRef.current = _value
136+
})
137+
138+
const stateRef = React.useRef(state)
139+
React.useEffect(() => {
140+
stateRef.current = state
141+
})
142+
143+
const nameRef = React.useRef(name)
144+
React.useEffect(() => {
145+
nameRef.current = name
146+
})
147+
148+
const typeRef = React.useRef(type)
149+
React.useEffect(() => {
150+
typeRef.current = type
151+
})
152+
153+
const formatOnBlurRef = React.useRef(formatOnBlur)
154+
React.useEffect(() => {
155+
formatOnBlurRef.current = formatOnBlur
156+
})
157+
158+
const formatRef = React.useRef(format)
159+
React.useEffect(() => {
160+
formatRef.current = format
161+
})
162+
163+
const parseRef = React.useRef(parse)
164+
React.useEffect(() => {
165+
parseRef.current = parse
166+
})
167+
168+
const formRef = React.useRef(form)
169+
React.useEffect(() => {
170+
formRef.current = form
171+
})
172+
173+
const componentRef = React.useRef(component)
174+
React.useEffect(() => {
175+
componentRef.current = component
176+
})
177+
133178
const handlers = {
134179
onBlur: React.useCallback(
135180
(event: ?SyntheticFocusEvent<*>) => {
136-
state.blur()
137-
if (formatOnBlur) {
181+
stateRef.current.blur()
182+
if (formatOnBlurRef.current) {
138183
/**
139184
* Here we must fetch the value directly from Final Form because we cannot
140185
* trust that our `state` closure has the most recent value. This is a problem
141186
* if-and-only-if the library consumer has called `onChange()` immediately
142187
* before calling `onBlur()`, but before the field has had a chance to receive
143188
* the value update from Final Form.
144189
*/
145-
const fieldState: any = form.getFieldState(state.name)
146-
state.change(format(fieldState.value, state.name))
190+
const fieldState: any = formRef.current.getFieldState(stateRef.current.name)
191+
stateRef.current.change(formatRef.current(fieldState.value, stateRef.current.name))
147192
}
148193
},
149-
// eslint-disable-next-line react-hooks/exhaustive-deps
150-
[state.blur, state.name, format, formatOnBlur]
194+
[]
151195
),
152196
onChange: React.useCallback(
153197
(event: SyntheticInputEvent<*> | any) => {
@@ -156,17 +200,17 @@ function useField<FormValues: FormValuesShape>(
156200
const targetType = event.target.type
157201
const unknown =
158202
~['checkbox', 'radio', 'select-multiple'].indexOf(targetType) &&
159-
!type &&
160-
component !== 'select'
203+
!typeRef.current &&
204+
componentRef.current !== 'select'
161205

162206
const value: any =
163-
targetType === 'select-multiple' ? state.value : _value
207+
targetType === 'select-multiple' ? stateRef.current.value : _valueRef.current
164208

165209
if (unknown) {
166210
console.error(
167211
`You must pass \`type="${
168212
targetType === 'select-multiple' ? 'select' : targetType
169-
}"\` prop to your Field(${name}) component.\n` +
213+
}"\` prop to your Field(${nameRef.current}) component.\n` +
170214
`Without it we don't know how to unpack your \`value\` prop - ${
171215
Array.isArray(value) ? `[${value}]` : `"${value}"`
172216
}.`
@@ -176,19 +220,17 @@ function useField<FormValues: FormValuesShape>(
176220

177221
const value: any =
178222
event && event.target
179-
? getValue(event, state.value, _value, isReactNative)
223+
? getValue(event, stateRef.current.value, _valueRef.current, isReactNative)
180224
: event
181-
state.change(parse(value, name))
225+
stateRef.current.change(parseRef.current(value, nameRef.current))
182226
},
183-
// eslint-disable-next-line react-hooks/exhaustive-deps
184-
[_value, name, parse, state.change, state.value, type]
227+
[]
185228
),
186229
onFocus: React.useCallback(
187230
(event: ?SyntheticFocusEvent<*>) => {
188-
state.focus()
231+
stateRef.current.focus()
189232
},
190-
// eslint-disable-next-line react-hooks/exhaustive-deps
191-
[state.focus]
233+
[]
192234
)
193235
}
194236

0 commit comments

Comments
 (0)