15

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?

AndrewL64
  • 15,794
  • 8
  • 47
  • 79
Maksim Boltik
  • 321
  • 1
  • 2
  • 6

5 Answers5

23

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

Francesco Meli
  • 2,484
  • 2
  • 21
  • 52
  • 1
    While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - [From Review](/review/low-quality-posts/23215190) – Tomasz Mularczyk Jun 07 '19 at 14:31
  • Fixed what you asked. Thanks. – Francesco Meli Jun 07 '19 at 14:39
12

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

likerRr
  • 1,248
  • 1
  • 13
  • 19
  • @likerRr, a valid solution is described below – Francesco Meli May 28 '20 at 11:58
  • It does not work. `this.props.meta.active` does not get true. If I remove it, it hits back my callback but then it gets into 'maximum depth reached...'. Must be needing an update? I am using react-final-form 6.5.7 – Arvind K. Jan 07 '22 at 12:41
  • @ArvindK. Example from the answer works perfectly. `active` becomes true when a field gets a focus. Probably your field doesn't get it. It might be the case when you use a custom field component. In this case you need manually call `onBlur` and `onFocus` when they happen. After that `active` will work correctly. But this is only for custom fields. Default one, like input=text or select should work out of the box. I updated the original answer, try the other solutions as well. – likerRr Jan 12 '22 at 21:35
  • @likerRr - Thank you for the update. I will check it soon! – Arvind K. Jan 13 '22 at 06:54
12

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>
    );
};
orszaczky
  • 13,301
  • 8
  • 47
  • 54
6

One could use the Field's parse attribute and provide a function that does what you need with the value:

<Field
  parse={value => {
    // Do what you want with `value`
    return value;
  }}
  // ...
/>
robsco
  • 559
  • 7
  • 13
1

You need to use the ExternalModificationDetector component to listen for changes on the field component like this:

    <ExternalModificationDetector name="abc">
      {externallyModified => (
        <BooleanDecay value={externallyModified} delay={1000}>
          {highlight => (
            <Field
                //field properties here
            />
          )}
        </BooleanDecay>
      )}
    </ExternalModificationDetector>

By wrapping a stateful ExternalModificationDetector component in a Field component, we can listen for changes to a field's value, and by knowing whether or not the field is active, deduce when a field's value changes due to external influences.

Via - React-Final-Form Github Docs


Here is a sandbox example provided in the React-Final-Form Docs: https://codesandbox.io/s/3x989zl866

AndrewL64
  • 15,794
  • 8
  • 47
  • 79