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

5

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?

Metathesize answered 20/5, 2018 at 15:49 Comment(0)
K
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://mcmap.net/q/756939/-how-to-listen-to-onchange-of-the-field-component-in-react-final-form

Kleon answered 21/5, 2018 at 10:36 Comment(4)
@likerRr, a valid solution is described belowTopaz
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.7Hug
@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.Kleon
@Kleon - Thank you for the update. I will check it soon!Hug
T
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

Topaz answered 7/6, 2019 at 14:13 Comment(2)
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 ReviewNonsuch
Fixed what you asked. Thanks.Topaz
K
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://mcmap.net/q/756939/-how-to-listen-to-onchange-of-the-field-component-in-react-final-form

Kleon answered 21/5, 2018 at 10:36 Comment(4)
@likerRr, a valid solution is described belowTopaz
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.7Hug
@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.Kleon
@Kleon - Thank you for the update. I will check it soon!Hug
I
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>
    );
};
Inimical answered 22/9, 2018 at 22:22 Comment(0)
C
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;
  }}
  // ...
/>
Clemenceau answered 6/11, 2019 at 19:46 Comment(0)
W
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

Wallis answered 20/5, 2018 at 16:34 Comment(1)
Maybe you could provide more simple example, that resolves my issue?Metathesize

© 2022 - 2024 — McMap. All rights reserved.