javascriptreactjsreact-final-form

How to listen to onChange of the Field component in React-Final-Form?


Redux-form "Field" component provides onChange property. A callback that will be called whenever an onChange event is fired from the underlying input. This callback allows to get "newValue" and "previousValue" for the Field.

React-final-form "Field" component doesn't have this property.

So, how I can get the same functionality?


Solution

  • 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