Ant design multiple select for nested objects

1.5k views Asked by At

I'm building a dashboard using refine.dev and AntDesign.

One of my entities has Many-to-Many relation that is returned in the API as an array of objects with id properties.

{
  "id": 1,
  "relations": [
    {
      "id": 1
    },
    {
      "id": 2
    }
  ]
}

I need to build a form with a “multiple select” component. And I follow this tutorial. But unfortunately, it has examples only for “many-to-one” relations.

First I generate selectProps. This part goes all right.

const { selectProps: relationsSelectProps } = useSelect<Relation>({
  resource: 'relations',
  optionLabel: 'name',
  defaultValue: record?.relations.map((r) => r.id),
});

Then starts the problem. When I'm trying to create a form item

<Form.Item
  name={['relations']}
>
  <Select
    mode="multiple"
    {...relationsSelectProps}
  />
</Form.Item>

I can't make it work with multiple nested objects.

I tried different name paths: ['relations', 'id'], and ['relations', '*', 'id']

Tried to play around with the normalize property.

I don't really want to flatten these objects on the backend side, so the question is: what's the best practice to make this work on the react side of the project?

2

There are 2 answers

0
Konstantin Bodnia On BEST ANSWER

So you can do this thing by adding two properties to the Form.Item component.

  1. getValueFromEvent to transform Select's values to form
  2. getValueProps to do it the other way around
<Form.Item
  name={['relations']}
  getValueFromEvent={(values) => {
    return values.map((id: number) => ({ id }));
  }}
  getValueProps={(ids) => {
    return ids?.map(({ id }: { id: number }) => id);
  }}
>
  <Select
    mode="multiple"
    {...selectProps}
  />
</Form.Item>

Then the post (patch) request's gonna look the right way:

{
  "relations": [
    { "id": 1 },
    { "id": 2 }
  ],
  // other fields
}
0
ben_g_123 On

So the first part regarding how you are trying to set the form item to line up with your model..

The "name" with the {[something, child-of-somthing]} on the Form.Item is basically dot notation

say I have model that has a relation in it e.g

  export interface ISubscription {
  id: number;
  sku: string;
  customer: ICustomer
}

and the ICustomer looks like this

    export interface ICustomer {
      id: number;
      name: string;
      surname:: string;
    }

when you set your Form.Item you can get to the nested value using the {[]} like

  <Form.Item
     label="Customer"
     name={["customer", "name"]}
     >
      <Input />               
    </Form.Item>

Secondly making multiple selects using antd is a lot more invloved when you go outside the basic use case in refine.dev.

I am not 100% sure with your use case but for example when I had to make a multiple select with custom tags in it.

<Form.Item label="Custom Tags">
  <Select
    mode="multiple"
    value={customTags}
    onSelect={handleSelect}
    onDeselect={handleDeselect}
  >
    {customTagOptions}
  </Select>
</Form.Item>

but you need to make your own custom options similar to this

  const customTagOptions = useMemo(() => {
    return tagDataResult?.data
      ?.map(
        // build an array with the id and label for the select option
        (t) => ({ id: t.id, label: `${t.tagType.name} : ${t.name}` })
      )
      .sort(
        // then sort everything
        (a, b) =>
          a.label.localeCompare(b.label, undefined, { sensitivity: "base" })
      )
      .map(
        // then build the final options
        (t) => (
          <Option key={t.id} value={t.id} title={t.label}>
            {t.label}
          </Option>
        )
      );
  }, [tagDataResult?.data]);

becuase yeah the antD / refine combo won;t let you do what you want out of the box.. you are fighting against the framework a bit here.

Hope this helps a bit man