DEV Community

Sabin Pandelovitch
Sabin Pandelovitch

Posted on

[Material-UI] Migrating from react-autosuggest

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 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

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
Enter fullscreen mode Exit fullscreen mode

Searched for a solution, found a full discussion on the matter here, also there is an issue opened here, 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

[
    { _id: "5c1b6690468fa31f86286825", name: "Afghanistan", __v: 0, eu: false },
    ...
    { _id: "5c1b6690468fa31f86286918", name: "Zimbabwe", __v: 0, eu: false }
]
Enter fullscreen mode Exit fullscreen mode

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

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;
Enter fullscreen mode Exit fullscreen mode

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

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

The component is still in the Lab phase but it can be integrated and used as ease.

Below is the new code written for the same test case

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;
Enter fullscreen mode Exit fullscreen mode

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
  2. Autocomplete component API

Live demo

You can find a working sandbox here

Top comments (0)