skip to Main Content

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.

2

Answers


  1. Chosen as BEST ANSWER

    Thanks to @slax57, I was having a hard time making it work (I tried the FormDataConsumer but couldn't make it work right). I will put my answer here because I included the solution to change the input "unit" too. Now the onChange accepts 2 sources.

    export const ArrayInputForIngredients = () => {
        const { setValue } = useFormContext();
        const onChange = (sourceOne, sourceTwo) => (_id, record) => {
          setValue(sourceOne, record?.name);
          setValue(sourceTwo, record?.unit);
        };
        return (
          <ArrayInput source="ingredients">
            <SimpleFormIterator inline>
              <FormDataConsumer>
                {({ getSource }) => (
                  <ReferenceInput source={getSource("_id")} reference="ingredients">
                    <AutocompleteInput
                      label="Ingrediente"
                      source="_id"
                      optionText={"name"}
                      onChange={onChange(getSource("name"), getSource("unit"))}
                    />
                  </ReferenceInput>
                )}
              </FormDataConsumer>
              <TextInput source="name" />
              <TextInput source="unit" />
            </SimpleFormIterator>
          </ArrayInput>
        );
      };
    

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

    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

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search