Search code examples
reactjsreact-hooksmaterial-uimui-autocomplete

MUI autocomplete on array of object return one dimensional array


I have the following Autocomplete component:

    <Autocomplete
           options={channels}
           autoHighlight
           multiple
           disableCloseOnSelect
           value={form.channel_ids}
           getOptionLabel={option => option.name}
           onChange={(event, value) => updateForm({...form, channel_ids: value?.map(channel => channel.id)})}
           renderInput={params => 
                 <TextField
                     {...params}
                     label="Channels"
                     error={!!errors?.channel_ids}
                     helperText={errors?.channel_ids?.[0]}
                  />
             }
             isOptionEqualToValue={(option, value) => option.id === value}
             renderOption={(props, option, {selected}) =>
                    <li {...props}>
                          <Checkbox
                             icon={<Icon path={mdiCheckboxBlankOutline} size={1} />}
                             checkedIcon={<Icon path={mdiCheckboxMarked} size={1} />}
                             style={{marginRight: 8}}
                             checked={selected}
                      />
    
                       {option.name}
                   </li>
             }
/>

The channels array:

[
   {id: 1, name: 'Example 1'},
   {id: 2, name: 'Example 2'}
]

So far this is working fine:

  • It shows the options with a checkbox;
  • It checks the checkboxes;
  • It renders the option label;

What isn't working is that it doesn't match the value and options. I want the Autocomplete to use an array of objects as options but to return a one dimensional array of selected ids. For example [1], [2] or [1, 2].

I hope someone can help me with this. Thanks in advance.


Solution

  • Long story short it's not possible to do it straight away. I read the component's documentation and it is not possible. What I can suggest to you, however, is to keep an external map to do the mapping ID-component in a work-around fashion.

    const options = new Map(channels.map((channel) => ([channel.id, channel])))
    
    // Since the option is mapped from the id the empty string will never be picked
    const getOptionName = (option: string) => options.get(option)?.name ?? ''
    
    
    <Autocomplete
               options={Array.from(options.keys())} // Modified
               autoHighlight
               multiple
               disableCloseOnSelect
               value={form.channel_ids}
               getOptionLabel={getOptionName} // Modified
               onChange={(event, value) => updateForm({...form, channel_ids: value})} // Modified
               renderInput={params => 
                     <TextField
                         {...params}
                         label="Channels"
                         error={!!errors?.channel_ids}
                         helperText={errors?.channel_ids?.[0]}
                      />
                 }
                 isOptionEqualToValue={(option, value) => option === value}
                 renderOption={(props, option, {selected}) =>
                        <li {...props}>
                              <Checkbox
                                 icon={<Icon path={mdiCheckboxBlankOutline} size={1} />}
                                 checkedIcon={<Icon path={mdiCheckboxMarked} size={1} />}
                                 style={{marginRight: 8}}
                                 checked={selected}
                          />
        
                           {getOptionName(option)} // Modified
                       </li>
                 }
    />
    

    Notice: if you think the computation of the map is too expensive in terms of time you can always use a memo.

    const options = useMemo(() => new Map(channels.map((channel) => ([channel.id, channel]))),[channels])