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
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 }
]
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;
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;
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
Live demo
You can find a working sandbox here
Top comments (0)