react-admin: FormDataConsumer: The is not updated with newly fetched data when the `formData` changes and a `filter` exists

What you were expecting: With <FormDataConsumer>, the <ReferenceInput> to be updated with different choices every time formData (or a particularly defined key) is changing, when we use a filter (eg: filter={{cat_id: formData.cat_id}} )

What happened instead: The choices are not updated, they always show the initially fetched choices

Steps to reproduce: I use the following filter to get a subset of the referenced data as choices in the ReferenceInput:

filter={{cat_id: formData.cat_id}}

When changing the cat_id (through a different field in the form), the subCat_id field is not re-fetched with the new filtered value.

Related code:

<FormDataConsumer>
        {({formData, ...rest}) =>
          formData.cat_id && (
            <ReferenceInput label="SUBCATEGORY" source="subCat_id" reference="subCategories" allowEmpty
                            perPage={100}
                            filter={{cat_id: formData.cat_id}}
                            sort={{field: 'name', order: 'ASC'}}
                            fullWidth
                            {...rest}
            >
              <SelectInput optionText="name" />
            </ReferenceInput>
          )}
      </FormDataConsumer>

Other information:

Environment

  • React-admin version: Latest master, after a related fix with commit 5ee89998b04b54d7b458845e633de522c41bf162

  • Last version that did not exhibit the issue (if applicable):

  • React version:

  • Browser:

  • Stack trace (in case of a JS error):

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Comments: 16 (6 by maintainers)

Most upvoted comments

I believe that this problem can be solved with 2 actions:

APPROACH 1

  1. We could add a key={formData.cat_id} to the ReferenceInput so that it remounts on every change of the cat_id:
{({formData, ...rest}) =>
          formData.cat_id && (
            <ReferenceInput 
                      key={formData.cat_id}
                      label="SUBCATEGORY"
                     // ...all other props here
            >
              <SelectInput optionText="name" />
            </ReferenceInput>
          )}
  1. Create a PR with a change on the ReferenceInputController so that it always resets the value of the field to null when the component remounts (see last line below):
export class ReferenceInputController extends Component {
    constructor(props) {
        super(props);
        const { perPage, sort, filter } = props;
        // stored as a property rather than state because we don't want redraw of async updates
        this.params = { pagination: { page: 1, perPage }, sort, filter };
        this.debouncedSetFilter = debounce(this.setFilter.bind(this), 500);

        this.props.change(REDUX_FORM_NAME, this.props.source, null, false, false);
    }

UPDATE: This approach fails for editing existing records, as the form value is converted to null. So this approach is a no-go (ignore it), unless there is a better way to handling the edit part.

APPROACH 2

Another approach that I tried and worked was that I explicitly added a “dependsOnValue” prop (eg: .dependsOnValue = {formData.cat_id}). If this prop value changes, then the code in the controller is doing a refetch plus a “set form value to null” action at componentWillReceiveProps.

    componentWillReceiveProps(nextProps) {
        if (
            this.props.record.id !== nextProps.record.id ||
            this.props.dependsOnValue !== nextProps.dependsOnValue
        ) {
            this.fetchReferenceAndOptions(nextProps);
        } else if (this.props.input.value !== nextProps.input.value) {
            this.fetchReference(nextProps);
        }
        if (this.props.dependsOnValue !== nextProps.dependsOnValue) {
            this.props.change(REDUX_FORM_NAME, this.props.source, null, false, false);
        }
    }

What do you think?

If any of the approaches is OK to you, please let me know to submit the respective PR. Thanks.

Yes, the fix was incomplete and has been finalized in https://github.com/marmelab/react-admin/pull/2117 which will be released in 2.2.1. In the mean time, add a key prop to the ReferenceInput component:

<FormDataConsumer>
    {({ formData, ...rest }) =>
        formData.lang && (
        <ReferenceInput
            key={formData.lang}
            label="Category"
            source="category_id"
            reference="categories"
            filter={{ lang: formData.lang }}
            {...rest}
        >
            <SelectInput optionText="title" />
        </ReferenceInput>
        )
    }
</FormDataConsumer>

I’m putting my work around here in case anyone else lands on this page when trying to create an input that is calculated based on the value of another. This solution ultimately does not require the use of a FormDataConsumer.

First import useForm and useFormState

import React, { useEffect } from 'react';
import { useForm, useFormState } from 'react-final-form';

Next, add these two lines to your component. Note: ‘amount’ is the source for one of my input fields. It’s value being changed will trigger a change in my

const {change} = useForm();
 const { values : { amount }} = useFormState({ subscription: { values: true } });

Next, add this useEffect statement Note: ‘percentage’ is the input field that I want to automatically change based on the input of ‘amount’

useEffect(() => {
    change('percentage', ((amount / totalFinancingAmount) * 100).toFixed(2));
  }, [change, amount]);

Finally, I add the ‘percentage’ input, which automatically adjusts its value based off of the value inputted in the ‘amount’ field.

<TextInput disabled fullWidth label = 'Percentage' source = 'percentage' />