-
-
Notifications
You must be signed in to change notification settings - Fork 482
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
input.onChange has a new reference each time value changes #816
Comments
The issue lies here https://github.com/final-form/react-final-form/blob/master/src/useField.js#L184 ( |
This hits a pretty interesting topic about what should affect public API method identity. There is definitely a (blunt) way to solve this problem by moving all dependencies into mutable vars/refs const _valueRef = React.useRef(_value)
React.useEffect(() => {
_valueRef.current = _value
})
const stateRef = React.useRef(state)
React.useEffect(() => {
stateRef.current = state
})
const nameRef = React.useRef(name)
React.useEffect(() => {
nameRef.current = name
})
const typeRef = React.useRef(type)
React.useEffect(() => {
typeRef.current = type
})
// etc...
//...
onChange: React.useCallback(
(event: SyntheticInputEvent<*> | any) => {
//...
const value: any =
event && event.target
? getValue(event, stateRef.current.value, _valueRef.current, isReactNative)
: event
stateRef.current.change(parseRef.current(value, nameRef.current))
},
[]
),
onFocus: React.useCallback(
(event: ?SyntheticFocusEvent<*>) => {
stateRef.current.focus()
},
[]
) Thus we don't need to depend on any direct reference from closure. But this will break a test introduced by #787 https://github.com/final-form/react-final-form/pull/787/files#diff-2824eafc0ee519762539c9b997ab9f64d71babadb83e52ec1cf2d7e5ef0e40ccR177 requiring the identities of functions to change on every But this means we have to always put explicit dependencies in mutable vars/refs which might not be the most aesthetic thing to do. On the other hand we guarantee that identity of the methods are stable from the very start, the same way react guarantees that dispatch or setState identity is preserved throughout rerenders. Unstable method identity is unnecessary and annoying but I am myself conflicted about the solution above so wdyt? (cc: @erikras) |
That's the winning argument, I think. Callbacks a library gives you should be instance-stable across renders. This will be a breaking change. @kapral18, can you PR this change? |
Here's a test I wrote when investigating this. Please include it in the PR. it('should give same instance of handlers as value changes', () => {
const spy = jest.fn()
const MyField = ({ name }) => {
const { input } = useField(name, { subscription: { value: true } })
const { onChange, onFocus, onBlur } = input
spy(onChange, onFocus, onBlur)
return <input {...input} />
}
render(
<Form onSubmit={onSubmitMock}>
{() => (
<form>
<MyField name="myField" />
</form>
)}
</Form>
)
expect(spy).toHaveBeenCalledTimes(2)
const [onChange, onFocus, onBlur] = spy.mock.calls[1]
const setValue = value => {
act(() => {
onFocus()
onChange(value)
onBlur()
})
}
setValue('dog')
expect(spy).toHaveBeenCalledTimes(3)
expect(spy.mock.calls[2][0]).toBe(spy.mock.calls[1][0]) // onChange
expect(spy.mock.calls[2][1]).toBe(spy.mock.calls[1][1]) // onFocus
expect(spy.mock.calls[2][2]).toBe(spy.mock.calls[1][2]) // onBlur
setValue('cat')
expect(spy).toHaveBeenCalledTimes(4)
expect(spy.mock.calls[3][0]).toBe(spy.mock.calls[1][0]) // onChange
expect(spy.mock.calls[3][1]).toBe(spy.mock.calls[1][1]) // onFocus
expect(spy.mock.calls[3][2]).toBe(spy.mock.calls[1][2]) // onBlur
expect(spy.mock.calls[3][0]).toBe(spy.mock.calls[2][0]) // onChange
expect(spy.mock.calls[3][1]).toBe(spy.mock.calls[2][1]) // onFocus
expect(spy.mock.calls[3][2]).toBe(spy.mock.calls[2][2]) // onBlur
}) |
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 final-form#816
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
Published fix in |
Are you submitting a bug report or a feature request?
Bug report
What is the current behavior?
Every time a field value changes, onChange gets a new reference (and maybe other event handlers do as well).
What is the expected behavior?
When building a custom field and using
useEffect
, it's frequent to depend ononChange
to update a value. In my case for a large field, I useuseMemo
to make sure I only rerender the parts that need it, butonChange
makes sure everything gets updated every time the value changes. Is there another way thanuseCallback(input.onChange, [])
?Sandbox Link
https://codesandbox.io/s/react-final-form-third-party-components-example-lo4yo?file=/index.js
What's your environment?
(Latest on every lib, as seen on the sandbox)
Other information
/
Thanks in advance!
The text was updated successfully, but these errors were encountered: