53

I'm trying to create a Material UI Autocomplete component that essentially just displays search results to the user. Some of the options' names will be duplicates, but they will all have unique IDs. I receive the following warning:

index.js:1 Warning: Encountered two children with the same key, Name B. Keys should be unique so that components maintain their identity across updates. Non-unique keys may cause children to be duplicated and/or omitted — the behavior is unsupported and could change in a future version.

const SearchField = () => {
    const [open, setOpen] = React.useState(false)
    const [searchQuery, setSearchQuery] = React.useState('')
    const [searchResults, setSearchResults] = React.useState([])
    const loading = true //later

    const debounced = useDebouncedCallback(
        async searchQuery => {
            if (searchQuery) {
                let result = await doSearch(searchQuery)
                if (result.status === 200) {
                    setSearchResults(result.data)
                } else {
                    console.error(result)
                }
            }
        },
        1000
    )

    const handleInputChange = e => {
        if (e.target.value && e.target.value !== searchQuery) {
            debounced(e.target.value)
            setSearchQuery(e.target.value)
        }
    }

    const options = [{
        name: 'Name A',
        id: 'entry_0597856'
    },{
        name: 'Name B',
        id: 'entry_3049854'
    },{
        name: 'Name B',
        id: 'entry_3794654'
    },{
        name: 'Name C',
        id: 'entry_9087345'
    }]


    return (
        <Autocomplete
            id='search_freesolo'
            freeSolo
            selectOnFocus
            clearOnBlur
            handleHomeEndKeys
            autoHighlight
            onInputChange={handleInputChange}
            open={true}
            onOpen={() => setOpen(true)}
            onClose={() => setOpen(false)}
            loading={loading}
            key={option => option.id}

            options={options}
            getOptionLabel={option => option.name}

            renderOption={(props, option) => (
                <Box
                    component='li'
                    {...props}
                >
                    {option.name}
                </Box>
            )}

            renderInput={params => {
                return (
                    <TextField
                        {...params}
                        required
                        id="search_bar"
                        label="Search"
                        InputProps={{
                            ...params.InputProps,
                            endAdornment: (
                                <React.Fragment>
                                    {loading ? <CircularProgress size={18} /> : null}
                                    {params.InputProps.endAdornment}
                                </React.Fragment>
                            )
                        }}
                    />
                )}
            }
            
        />
    )
}
Olivier Tassinari
  • 8,238
  • 4
  • 23
  • 23
kougami
  • 696
  • 1
  • 6
  • 14

1 Answers1

111

You can define your own renderOption that can return the list item with a correct key value. Your code complains about the duplicated keys because by default, Autocomplete uses the getOptionLabel(option) to retrieve the key:

<Autocomplete
  renderOption={(props, option) => {
    return (
      <li {...props} key={option.id}>
        {option.name}
      </li>
    );
  }}
  renderInput={(params) => <TextField {...params} label="Movie" />}
/>

If it still doesn't work, check your props order, you need to declare the key prop last, if you put it before the props provided by the callback:

<Box component='li' key={key} {...props}

Then it will be overridden by the props.key from MUI. It should be like this:

<Box component='li' {...props} key={key}

Live Demo

Codesandbox Demo

NearHuscarl
  • 66,950
  • 18
  • 261
  • 230
  • This works, thank you, however I am hoping to add a few other things later that might not work well inside a
  • (e.g an icon, a date in brackets, things like that). I presume that I can just add a div or a span with the key={option.id} on that, but so I can better understand - what was wrong with my original code? Why does it work fine with
  • but not with , even when I add the key={option.id} to the ? Thank you
  • – kougami Sep 30 '21 at 16:59
  • 1
    @kougami it works fine with `Box`, I've updated the live demo for you. I use `li` for demonstration purpose only (simpler code, easier to understand). – NearHuscarl Sep 30 '21 at 17:02
  • 1
    I'm afraid it isn't working like that for me - when I use , option.name seems to be being used for the key. I can see it's working like that in the demo, however the code in the demo is simply from the MUI documentation , rather than my own code, if I'm not mistaken? – kougami Sep 30 '21 at 17:06
  • 5
    @kougami you need to declare the `key` prop last, if you put it before the `props` provided by the callback like ` – NearHuscarl Sep 30 '21 at 17:10
  • 3
    That's mad, that it was something so simple. But now I've learnt something, I'll put {...props} first in future! Thanks very much for your help and for helping me to understand. – kougami Sep 30 '21 at 17:12
  • This helped me a ton, I didn't realize that the key prop needed to go after the spread. Might be good to note in the answer. – domshyra Oct 27 '21 at 15:30
  • 1
    What is the procedure for Material UI v4? There isn't a `props` parameter into `renderOption`, only `option` and `state`. – Kaspar Poland Dec 28 '21 at 21:52
  • this is assuming you've got an ID in your data model. – Steven Feb 15 '23 at 21:33