Home > Net >  Always Display Default Option with React Material-UI Autocomplete
Always Display Default Option with React Material-UI Autocomplete

Time:09-24

I'm using the Material-UI Autocomplete in React for an address search/verification form, and I'd like there to always be a default option that shows up no matter the results.

In Angular I used [displayWith] with a function to tell it to always display an item from the array with a specific ID, but I'm not sure how to do it in React. I've been trying to figure out filterOptions, but I don't want it to filter based on anything related to input, I just want it to always be an option.

In this simplified example, if I wanted it to always show the film with the year: 1111, how would I do that?

import React, { useState, ChangeEvent } from 'react';
import {
  TextField,
  InputAdornment
} from "@material-ui/core";
import Autocomplete from '@material-ui/lab/Autocomplete';
import { Search } from "@material-ui/icons";
import axios from "axios";

const AddressSearch = (props) => {

  const [addressesList, setAddressesList] = useState([]);
  const [inputAddress, setInputAddress] = useState<string>("");

  const handleAddressChange = (event: ChangeEvent<{ value: unknown }>) => {
    setInputAddress(event.target.value as string);
  };

  const topFilms = [
    { title: 'I HATE MOVIES', year: 1111 },
    { title: 'The Shawshank Redemption', year: 1994 },
    { title: 'The Godfather'},
    { title: 'The Godfather: Part II', year: 1974 }
  ]

  return (
    <div>
      <Autocomplete
        id="address-autocomplete"
        freeSolo
        options={topFilms}
        getOptionLabel={(option) => option.title}
        popupIcon={<Search />}
        renderInput={(params) => <TextField 
          id="address-input"                          
          {...params} 
          onChange={handleAddressChange}
          placeholder="Quickly find your address" 
          InputProps={{ ...params.InputProps,
          startAdornment: (
            <InputAdornment position="start"><Search /></InputAdornment>
          )}}
        /> }
      />
    </div>
  );
}

export default AddressSearch;

Note: the website I'm working with is using Material-UI v 4.12

CodePudding user response:

You can create a wrapper of filterOptions without touching anything in the original filterOptions. Simply filter as normal, get the results and see if there is a default option and if there isn't, add it again:

import TextField from "@mui/material/TextField";
import Autocomplete, { createFilterOptions } from "@mui/material/Autocomplete";

const _filterOptions = createFilterOptions();
const filterOptions = (options, state) => {
  const results = _filterOptions(options, state);

  if (!results.includes(myDefaultOption)) {
    results.unshift(myDefaultOption);
  }

  return results;
};

export default function ComboBox() {
  return (
    <Autocomplete
      filterOptions={filterOptions}
      options={options}
      sx={{ width: 300 }}
      renderInput={(params) => <TextField {...params} label="Movie" />}
    />
  );
}

const myDefaultOption = { label: "My default option", year: 1994 };

const options = [
  myDefaultOption,
  ...
]

Live Demo

Codesandbox Demo

CodePudding user response:

Edit: Bassed on your comment, you want to take the fixed values from the source, so I made some changes.

Edit: Added your year: 1111 example.

CodeSandbox

...

You can add filterOptions Docs to the Autocomplete element, then you add the fixed values to the result.

Filter options and source: Link to documentation

const filterOptions = createFilterOptions({
  ignoreCase: true,
  ignoreAccents: true
})

const topFilms = [
  { title: 'Elysium', year: 2013 },
  { title: 'I HATE MOVIES', year: 1111 },
  { title: 'The Shawshank Redemption', year: 1994 },
  { title: 'The Godfather' },
  { title: 'The Godfather: Part II', year: 1974 }
]

The addressesList state stores the titles of the values you want fixed, I took this hook from your code

const [addressesList, setAddressesList] = useState([1111])

Taking away the fixed values from the source, so they'll not repeat, this filter will only be triggered once, or if the source, or the "addressesList" changes.

const fixedValues = useMemo(() => topFilms.filter(e => addressesList.includes(e.year)), [topFilms, addressesList])
const dynamicValues = useMemo(() => topFilms.filter(e => !addressesList.includes(e.year)), [topFilms, addressesList])

Changes in the Autocomplete component.

<Autocomplete
   filterOptions={(options, _ref) => [...fixedValues, ...filterOptions(options, _ref)]}
   ...Other attributes
/>

Final Code:

import React, { useState, ChangeEvent, useMemo } from 'react'
import { TextField, InputAdornment } from "@material-ui/core"
import Autocomplete, { createFilterOptions } from '@material-ui/lab/Autocomplete'
import { Search } from "@material-ui/icons"

const filterOptions = createFilterOptions({
  ignoreCase: true,
  ignoreAccents: true
})

const topFilms = [
  { title: 'Elysium', year: 2013 },
  { title: 'I HATE MOVIES', year: 1111 },
  { title: 'The Shawshank Redemption', year: 1994 },
  { title: 'The Godfather' },
  { title: 'The Godfather: Part II', year: 1974 }
]

const AddressSearch = (props) => {

  const [addressesList, setAddressesList] = useState([1111])
  const [inputAddress, setInputAddress] = useState("")

  const handleAddressChange = (event) => {
    setInputAddress(event.target.value)
  }

  const fixedValues = useMemo(() => topFilms.filter(e => addressesList.includes(e.year)), [topFilms, addressesList])
  const dynamicValues = useMemo(() => topFilms.filter(e => !addressesList.includes(e.year)), [topFilms, addressesList])

  return (
    <div>
      <Autocomplete
        id="address-autocomplete"
        freeSolo
        options={dynamicValues}
        getOptionLabel={(option) => option.title}
        popupIcon={<Search />}
        filterOptions={(options, _ref) => [...fixedValues, ...filterOptions(options, _ref)]}
        renderInput={(params) => <TextField
          id="address-input"
          {...params}
          onChange={handleAddressChange}
          placeholder="Quickly find your address"
          InputProps={{
            ...params.InputProps,
            startAdornment: (
              <InputAdornment position="start"><Search /></InputAdornment>
            )
          }}
        />}
      />
    </div>
  )
}

export default AddressSearch
  • Related