Search code examples
reactjsreact-admin

Using OnChange, how to change values inside an <ArrayInput> <SimpleFormIterator><ReferenceInput> in React-Admin


I have the following component which I'm trying to use inside a "Drinks Edit View". In "drinks" I have nested "ingredients", but the ingredients are also a related resource.

I want to select the ingredient from the related resource and set the nested fields (in drink) "record.ingredients[idx].name", "record.ingredients.[idx]._id", "record.ingredients.[idx].unit".

But I can't change the nested, even if I use an index [0] to test, it doesn't work and I don't know how to get the index too.

The way it is now, for example if I change the ingredient, the name of the Drink will be changed because the component is inside the Drink Edit View .

I tried FormDataConsumer, RecordContext without success.

import {
    AutocompleteInput, ArrayInput, SimpleFormIterator, ReferenceInput, TextInput, NumberInput, BooleanInput,
    AutocompleteInputProps, FormDataConsumer,
} from 'react-admin';
import { useFormContext } from 'react-hook-form';

export const ArrayInputForIngredients = () => {
    const { setValue } = useFormContext();
    const handleIngredientChange: AutocompleteInputProps['onChange'] = (
        value,
        record
    ) => {
        setValue('name', record?.name); // NOT WORKING, CHANGES THE NAME OF THE DRINK
        setValue('unit', record?.unit);
        setValue('_id', record?._id);
    };

    return (
        <ArrayInput source="ingredients">
            <SimpleFormIterator inline >
                <ReferenceInput source="_id" reference="ingredients" >
                    <AutocompleteInput label="Ingrediente" 
                    source="_id" 
                    optionText={"name"} 
                    onChange={handleIngredientChange} />
                </ReferenceInput>
                <TextInput source="name" />
                <TextInput source="unit" />
                <BooleanInput source="decorative" />
                <BooleanInput source="optional" />
                <NumberInput source="quantity" />
            </SimpleFormIterator>
        </ArrayInput>
    )
}

The component is inside something (simplified) like this:

export const DrinksEdit = () => {
    return (
        <Edit>
        <SimpleForm>
        <TextInput source= "name" />
        <ArrayInputForIngredients />
        < /SimpleForm>
        < /Edit>
    )
};

The name and other data of the each ingredient I select in the AutoCompleteInput to change and not the name of the drink.


Solution

  • If I understand correctly, you need to pre-fill the values of name and unit based on the referenced ingredient selected, right?

    Answer for react-admin v4

    If so, calling setValue('name', record?.name) directly can't work because you need to use FormDataConsumer's getSource to compute the source path for the input nested inside ArrayInput.

    One way to achieve that is to use a HOF like this:

    const TagsInput = () => {
      const { setValue } = useFormContext();
      const onChange = (source) => (_id, record) => {
        setValue(source, record?.name?.en);
      };
      return (
        <ArrayInput source="tags">
          <SimpleFormIterator inline>
            <FormDataConsumer>
              {({ getSource }) => (
                <ReferenceInput source={getSource("id")} reference="tags">
                  <AutocompleteInput
                    label="Tags"
                    source="id"
                    optionText={"name.en"}
                    onChange={onChange(getSource("name"))}
                  />
                </ReferenceInput>
              )}
            </FormDataConsumer>
            <TextInput source="name" />
          </SimpleFormIterator>
        </ArrayInput>
      );
    };
    

    And then

    export const PostEdit = () => {
        return (
            <Edit>
              <SimpleForm>
                <TextInput source= "name" />
                <TagsInput />
              </SimpleForm>
            </Edit>
        )
    };
    

    Working example in the following sandbox (with tags input, in PostEdit, miscellanous tab): https://codesandbox.io/p/devbox/young-platform-z7x5lf


    EDIT: Updated sandbox for react-admin v5

    https://stackblitz.com/edit/github-vakown?file=src%2Fposts%2FPostEdit.tsx

    Relevant code:

    const TagAutocompleteInput = () => {
      const sourceContext = useSourceContext();
      const nameInputSource = sourceContext.getSource('name');
      const { setValue } = useFormContext();
      const onChange = (_id, record) => {
        setValue(nameInputSource, record?.name?.en);
      };
      return (
        <ReferenceInput source="id" reference="tags">
          <AutocompleteInput
            label="Tags"
            source="id"
            optionText={'name.en'}
            onChange={onChange}
          />
        </ReferenceInput>
      );
    };
    
    const TagsInput = () => (
      <ArrayInput source="tags">
        <SimpleFormIterator inline>
          <TagAutocompleteInput />
          <TextInput source="name" />
        </SimpleFormIterator>
      </ArrayInput>
    );
    

    This leverages useSourceContext from react-admin.