How to listen to onChange of the Field component in React-Final-Form?
React-final-form handles this functionality with a tiny external package.
Basically it is an additional component to add inside the form that binds to the element using its name:
<Field name="foo" component="input" type="checkbox" />
<OnChange name="foo">
{(value, previous) => {
// do something
}}
</OnChange>
The current documentation can be found here:
https://github.com/final-form/react-final-form-listeners#onchange
I haven't used redux-form, but I added a super simple wrapper around the Field component to listen to onChange like this:
const Input = props => {
const {
name,
validate,
onChange,
...rest
} = props;
return (
<Field name={name} validate={validate}>
{({input, meta}) => {
return (
<input
{...input}
{...rest}
onChange={(e) => {
input.onChange(e); //final-form's onChange
if (onChange) { //props.onChange
onChange(e);
}
}}
/>
)}}
</Field>
);
};
The idea under change detection is to subscribe to value changes of Field
and call your custom onChange
handler when value actually changes. I prepared simplified example where you can see it in action. Details are in MyField.js
file.
As the result you can use it just as with redux-form
:
<MyField
component="input"
name="firstName"
onChange={(val, prevVal) => console.log(val, prevVal)}
/>
2022 JANUARY UPDATE
While the code above still works (check the sandbox version) there is a case when the solutions requires more tweeks around it.
Here is an updated sandbox with an implementation via the hooks. It's based on a useFieldValue
hook and OnChange
component as a consumer of this hook. But the hook itself can be used separately when you need previous value between re-renders. This solution doesn't rely on meta.active
of the field.
// useFieldValue.js
import { useEffect, useRef } from "react";
import { useField } from "react-final-form";
const usePrevious = (val) => {
const ref = useRef(val);
useEffect(() => {
ref.current = val;
}, [val]);
return ref.current;
};
const useFieldValue = (name) => {
const {
input: { value }
} = useField(name, { subscription: { value: true } });
const prevValue = usePrevious(value);
return [value, prevValue];
};
export default useFieldValue;
// OnChange.js
import { useEffect } from "react";
import useFieldValue from "./useFieldValue";
export default ({ name, onChange }) => {
const [value, prevValue] = useFieldValue(name);
useEffect(() => {
if (value !== prevValue) {
onChange(value, prevValue);
}
}, [onChange, value, prevValue]);
return null;
};
Another nice option is this answer: https://stackoverflow.com/a/56495998/3647991