Autocomplete provides a superior user experience to your app’s visitors. It also helps to avoid redundant spelling errors while searching.
In this post, we’ll build a React auto-complete component from scratch.
Then, you can just share it to your Bit collection, and use it in all your apps! It will become a reusable Lego-piece you can use to build faster and share.
Bit - Component Discovery and Collaboration
You can get the options from an AJAX request through API, or database. You have a choice for the options shown. Just load them into an array.
Here’s what the end-product will look like.
What we’re building
The code for the project is available here on codesandbox, and is also embedded at the end of the post.
App
>Autocomplete
The Autocomplete component has a container component App, it passes the options to the Autocomplete component in an Array.
import React, { Component } from 'react';
import './App.css';
import Autocomplete from './Autocomplete';
const App = () => {
return (
<div className="App">
<Autocomplete
options={[
'Papaya',
'Persimmon',
'Paw Paw',
'Prickly Pear',
'Peach',
'Pomegranate',
'Pineapple'
]}
/>
</div>
);
};
export default App;
Autocomplete Component
This is where everything happens. I grabbed the initial search box from here on codepen. Emptied default index.css and filled with new css.
Here’s how the initial Autocomplete component looks like.
import React, { Component } from 'react';
export class Autocomplete extends Component {
render() {
return (
<React.Fragment>
<div className="search">
<input type="text" className="search-box" />
<input type="submit" value="" className="search-btn" />
</div>
</React.Fragment>
);
}
}
export default Autocomplete;
Data Validation
Autocomplete component is useless without the options. Options need to be validated as an array to catch the data-type errors quickly. React PropTypes do this exact thing.
They can also flag props as mandatory or set default values.
import PropTypes from 'prop-types';
Options can be marked as Mandatory and Array type within the class by using
static propTypes = {
options: PropTypes.instanceOf(Array).isRequired;
};
If you do not pass options from parent component, it’ll throw an error on the console.
Here’s the output of our code so far…
Well, it does nothing.
User Inputs
A user can:
- Change the active option with up/down arrow keys.
- Select option by clicking with a mouse-click or pressing Return (Enter) key.
Methods Required:
onChange: to check options when input changes
onKeyDown: to check return and arrow keys
value: onChange blocks user from typing into the input field, so we have to fill the value this way.
States Required:
showOptions: boolean (true/false)
filteredOptions: array of items that match with user input.
activeOption: location of currently selected item in filteredOptions, index (Number).
optionList will render JSX with options (in
- ) that user can choose from. The rendered JSX uses states, and is re-rendered when state is changed.
There will be a lot of places to use this.state.filteredOptions or this.onChange. I like to keep names short, so I used object destructuring for all states and methods.
Here’s how Autocomplete looks now.
import React, { Component } from 'react';
import PropTypes from 'prop-types';
export class Autocomplete extends Component {
static propTypes = {
options: PropTypes.instanceOf(Array).isRequired
};
state = {
activeOption: 0,
filteredOptions: [],
showOptions: false,
userInput: ''
};
render() {
const {
onChange,
onChange,
onKeyDown,
userInput,
state: { activeOption, filteredOptions, showOptions, userInput }
} = this;
let optionList;
return (
<React.Fragment>
<div className="search">
<input
type="text"
className="search-box"
onChange={onChange}
onKeyDown={onKeyDown}
value={userInput}
/>
<input type="submit" value="" className="search-btn" />
{optionList}
</div>
</React.Fragment>
);
}
}
export default Autocomplete;
onChange
When the user makes changes in the input field, we’d like a few things to happen.
onChange = (e) => {
const { options } = this.props;
const userInput = e.currentTarget.value;
const filteredOptions = options.filter(
(option) => option.toLowerCase().indexOf(userInput.toLowerCase()) > -1
);
this.setState({
activeOption: 0,
filteredOptions,
showOptions: true,
userInput
});
};
It gets options from props, options are used for suggestions. Also, sets userInput to target value (input field).
It filters the options to filteredOptions, the filtering condition being userInput sub-string of the value in array.
First item(index 0) infilteredOptions is the default selected item. This list directly affects the optionList.
onClick
onClick = (e) => {
this.setState({
activeOption: 0,
filteredOption: [],
showOptions: false,
userInput: e.currentTarget.innerText
});
};
It turns suggestions off and puts text from the clicked element into the input field.
onKeyDown
It handles keyDown events.
Return key (13) does the same thing as the click event, selects the item and puts a value to the input field.
Down arrow(40) selects the lower option. Up arrow (38) selects the upper option. But it won’t go below last or above the first option.
onKeyDown = (e) => {
const { activeOption, filteredOptions } = this.state;
if (e.keyCode === 13) {
this.setState({
activeOption: 0,
showSuggestions: false,
userInput: filteredOptions[activeOption]
});
} else if (e.keyCode === 38) {
if (activeOption === 0) {
return;
}
this.setState({ activeOption: activeOption - 1 });
} else if (e.keyCode === 40) {
if (activeOption - 1 === filteredOptions.length) {
return;
}
this.setState({ activeOption: activeOption + 1 });
}
};
If you use React Dev Tools, you can see the changes there…
Now, it’s time to get these state changes to the interface and let users select an option.
The optionList
optionList is the option selection interface for the end-user.
let optionList;
if (showOptions && userInput) {
if (filteredOptions.length) {
optionList = (
<ul className="options">
{filteredOptions.map((optionName, index) => {
let className;
if (index === activeOption) {
className = 'option-active';
}
return (
<li className={className} key={optionName} onClick={onClick}>
{optionName}
</li>
);
})}
</ul>
);
} else {
optionList = (
<div className="no-options">
<em>No Option!</em>
</div>
);
}
}
showOptions is true, and input area is not blank.
It goes through the filteredOptions to create a list. Additionally, active option gets option-active className. This is styled using css in index.css.
Here’s what it looks like.
If the input field value does not match with anything, it says no option.
Codesandbox demo is embedded below.
https://medium.com/media/75a78e88dcd5f394137e9bc5080ab4e8/href
Conclusion
So we’ve created an auto-complete component your users can enjoy, creating a better experience for your product. It will help them reduce confusion and mistakes, and quickly navigate their way through your application.
Please feel free to comment and ask me anything! I’d be happy to help :)
Learn more
- 5 Tools for Faster Development in React
- 11 React UI Component Libraries you Should Know in 2019
- 11 JavaScript Animation Libraries For 2019
Top comments (2)
Thank you so much for the recent posts @kris ~
& Bit looks like a great place to share ⚛ components 😮
And may I request to highlight codes using markdown code highlight syntax?
It'd make the code more readable.
Lastly, you can embed Sandboxes like following
(reference: dev.to Editor Guide)
I use cross post from medium seem need extra config