[Material-UI] Migrating from react-autosuggest
Tags: react, materialui
Published: 2020-03-05 16:02:16

In the past days I went back to an older React app to add some new features. Written in 2018-2019 using React 16.3, with [Material-UI](https://material-ui.com) at version 3.* something at the time.

One solution for autosuggest input from them back in the day from Material-UI, they recommended using some libraries one of which was [react-autosuggest](https://github.com/moroshko/react-autosuggest)

###The problem

Meanwhile my app was refactored, moved to React 16.9, Material-UI 4, hooks and what not. Everything was ok, or so I thought, util I saw some warning messages in the console

```
Warning: Using UNSAFE_componentWillReceiveProps in strict mode is not recommended and may indicate bugs in your code. See https://fb.me/react-unsafe-component-lifecycles for details.

* Move data fetching code or side effects to componentDidUpdate.
* If you're updating state whenever props change, refactor your code to use memoization techniques or move it to static getDerivedStateFromProps. Learn more at: https://fb.me/react-derived-state

Please update the following components: Autowhatever
```

Searched for a solution, found a full discussion on the matter [here](https://github.com/moroshko/react-autosuggest/issues/670), also there is an issue opened [here](https://github.com/Financial-Times/g-components/issues/89), but as the guys talk on GitHub, the project seems kind of abandoned.

###The use case
I'll take you through the implementation from my project for a single case, that of the country autocomplete.

The country list is an array of objects as follows

```js
[
    { _id: "5c1b6690468fa31f86286825", name: "Afghanistan", __v: 0, eu: false },
    ...
    { _id: "5c1b6690468fa31f86286918", name: "Zimbabwe", __v: 0, eu: false }
]
```

Here there are 2 cases to cover:
- Display the `name`, store the `_id`
- Display the `name`, store the whole object

Ok so let's dive into the code a bit and see how it looked with React-Autosuggest library.

*This is a simplified version of the code that I use, made to resemble the most with the one from [Material-UI tutorial](https://v3.material-ui.com/demos/autocomplete/)*

```js
import React, { useState } from 'react';
import * as PropTypes from 'prop-types';
import deburr from 'lodash/deburr';
import Autosuggest from 'react-autosuggest';
import match from 'autosuggest-highlight/match';
import parse from 'autosuggest-highlight/parse';
import TextField from '@material-ui/core/TextField';
import Paper from '@material-ui/core/Paper';
import MenuItem from '@material-ui/core/MenuItem';
import styles from './styles';

const renderSuggestion = (suggestion, { query, isHighlighted }) => {
  const matches = match(suggestion.name, query);
  const parts = parse(suggestion.name, matches);

  return (
    <MenuItem selected={isHighlighted} component="div">
      <div>
        {parts.map((part, index) => (part.highlight ? (
          <span key={String(index)} style={{ fontWeight: 500 }}>
              {part.text}
            </span>
        ) : (
          <strong key={String(index)} style={{ fontWeight: 300 }}>
            {part.text}
          </strong>
        )))}
      </div>
    </MenuItem>
  );
};

const getSuggestions = (suggestions, value) => {
  const inputValue = deburr(value.trim()).toLowerCase();
  const inputLength = inputValue.length;
  let count = 0;

  return inputLength === 0
    ? []
    : suggestions.filter((suggestion) => {
      const keep = count < 5 && suggestion.name.slice(0, inputLength).toLowerCase() === inputValue;

      if (keep) {
        count += 1;
      }

      return keep;
    });
};

const renderInputComponent = (inputProps) => {
  const {
    classes, inputRef = () => {
    }, ref, ...other
  } = inputProps;

  return (
    <TextField
      fullWidth
      InputProps={{
        inputRef: (node) => {
          ref(node);
          inputRef(node);
        },
        classes: {
          input: classes.input,
        },
      }}
      {...other}
    />
  );
};

const getSuggestionValue = suggestion => suggestion.name;

const AutosuggestInput = ({ inputValue, label, margin, formInput, handler, inputSuggestions, returnId }) => {
  const classes = styles();

  const [suggestions, setSuggestions] = useState([]);
  const [value, setValue] = useState(inputValue);

  const handleSuggestionsFetchRequested = ({ value }) => {
    setSuggestions(getSuggestions(inputSuggestions, value))
  };

  const handleSuggestionsClearRequested = () => {
    setSuggestions([])
  };

  const handleChange = () => (event, { newValue }) => {
    const suggestion = suggestions.filter(sug => sug.name === newValue);

    !suggestion.length && handler(formInput, '');

    setValue(newValue);
  };

  const onSuggestionSelected = (event, { suggestion }) => {
    handler(formInput, suggestion._id);
  };

  const autosuggestProps = {
    suggestions,
    renderInputComponent: renderInputComponent,
    onSuggestionsFetchRequested: handleSuggestionsFetchRequested,
    onSuggestionsClearRequested: handleSuggestionsClearRequested,
    getSuggestionValue: getSuggestionValue,
    renderSuggestion: renderSuggestion,
    onSuggestionSelected: onSuggestionSelected,
  };

  return (
    <div className={classes.root}>
      <Autosuggest
        {...autosuggestProps}
        inputProps={{
          classes,
          placeholder: label,
          value,
          onChange: handleChange(),
          autoComplete: 'no',
          margin,
        }}
        theme={{
          container: classes.container,
          suggestionsContainerOpen: classes.suggestionsContainerOpen,
          suggestionsList: classes.suggestionsList,
          suggestion: classes.suggestion,
        }}
        renderSuggestionsContainer={options => (
          <Paper {...options.containerProps} square>
            {options.children}
          </Paper>
        )}
      />
    </div>
  )
};

AutosuggestInput.propsTypes = {
  inputValue: PropTypes.string,
  label: PropTypes.string,
  margin: PropTypes.string,
  formInput: PropTypes.string.isRequired,
  handler: PropTypes.func.isRequired,
  inputSuggestions: PropTypes.array.isRequired,
  returnId: PropTypes.bool,
};

AutosuggestInput.defaultProps = {
  inputValue: '',
  label: '',
  margin: 'normal',
  returnId: false,
};

export default AutosuggestInput;
```

###The solution*
_*Solution that worked best for me, there are far more than one way of solving this_

It actually came from the same guys that recommended using this library in the first place. 

Time has past and the people from Material-UI developed their own solution for the [Autocomplete component](https://material-ui.com/components/autocomplete/)

Much more simplified, much more elegant, much more powerfully. 

The component is still in the [Lab phase](https://material-ui.com/components/about-the-lab/) but it can be integrated and used as ease.

Below is the new code written for the same test case

```js
import React from 'react';
import * as PropTypes from 'prop-types';

import { TextField } from '@material-ui/core';
import { Autocomplete } from '@material-ui/lab';

const AutocompleteInput = ({ input, options, value, label, margin, required, returnId, handler }) => (
  <Autocomplete
    options={options}
    getOptionLabel={option => (typeof option === 'string' && returnId ? options.find(c => c._id === option) : option)?.name}
    value={value}
    onChange={(event, newValue) => {
      handler(input, returnId ? newValue?._id : newValue)
    }}
    renderInput={params => <TextField {...params} label={label} margin={margin} required={required} />}
  />
);

AutocompleteInput.propTypes = {
  input: PropTypes.string.isRequired,
  options: PropTypes.array.isRequired,
  value: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
  label: PropTypes.string.isRequired,
  margin: PropTypes.string,
  required: PropTypes.bool,
  returnId: PropTypes.bool,
  handler: PropTypes.func.isRequired,
};

AutocompleteInput.defaultProps = {
  margin: 'normal',
  required: false,
  returnId: false,
  value: null,
};

export default AutocompleteInput;
```

This is a mere example of what it can do. It has much more potential and options available and I hope to see it in the Material-UI 5 core integrated.

For more information check the following links from Material-UI
1. [Autocomplete component](https://material-ui.com/components/autocomplete/)
2. [Autocomplete component API](https://material-ui.com/api/autocomplete/)

###Live demo
You can find a working sandbox [here](https://codesandbox.io/s/autosuggest-4vq1v)