Skip to content

Instantly share code, notes, and snippets.

@jacobvr
Created July 2, 2020 16:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jacobvr/4b425f8c1b748819f469b01ee294a4dd to your computer and use it in GitHub Desktop.
Save jacobvr/4b425f8c1b748819f469b01ee294a4dd to your computer and use it in GitHub Desktop.
Typeahead interview challenge
import React, { useState, useEffect, useRef } from "react";
import { arrayOf, string } from "prop-types";
import ReactDOM from "react-dom";
import "./styles.css";
/**
* Please note that this app is intentionally in a broken state. You must create your
* component and add it to `ReactDOM.render` (at the bottom of this file) to get started.
*/
/**
* Using React create a `Typeahead` component that takes `list` and `classname` props.
* Be sure that the component utilizes propTypes and any other best practices
* that you follow. Use the `carBrands` list which is defined below as the
* value for the `list` prop.
*
* Ensure that your component meets the following requirements:
* 1. As the user types in an input field, a list of options should appear below it.
* - The list should only appear when input is not empty. Whitespace is
* considered empty.
* - The list should contain items from the `list` prop that *start*
* with the user entered value. Matching should be case insensitive.
* - Every new character typed should filter the list.
* 2. Clicking on a list item should populate the input with the selected item's
* value and hide the list.
* 3. For visible option strings, style the substring the user has entered as
* *bold*.
* 4. Highlight a list item with gray background and white
* text when the user mouses over it.
* 5. The input and list should be navigable using the keyboard.
* - Using `tab` and `shift+tab`, the user should be able to focus the different
* list items.
* - With the cursor in the input, pressing the `tab` key should focus the
* first item with the default browser focus style.
* - Subsequent presses of the "tab" key should focus the next item in the list.
* - Pressing the `shift+tab` keys should focus the previous item in the list.
* - Pressing the `shift+tab` key when the first item is focused should focus
* the input again.
* - Mousing over other list items should highlight them while the keyboard-
* focused item remains focused.
* - Pressing the `tab` key when no list is visible should move focus away
* from the input.
* - Pressing the `return` key when an item is focused should populate the input
* with the focused item's value, hide the list, and focus the input
* again.
* - Pressing the `escape` key should close the list.
* 6. Clicking outside the input or the list should close the list.
*/
/**
* Please don't change the `carBrands` list.
*/
const carBrands = [
"Alfa Romeo",
"Audi",
"BMW",
"Chevrolet",
"Chrysler",
"Dodge",
"Ferrari",
"Fiat",
"Ford",
"Honda",
"Hyundai",
"Jaguar",
"Jeep",
"Kia",
"Mazda",
"Mercedez-Benz",
"Mitsubishi",
"Nissan",
"Peugeot",
"Porsche",
"SAAB",
"Subaru",
"Suzuki",
"Toyota",
"Volkswagen",
"Volvo"
];
const QueryItem = ({ query, item, handleKeyDown, handleItemClick }) => (
<li
tabIndex="0"
className="item"
onClick={() => handleItemClick(item)}
onKeyDown={e => handleKeyDown(e, item)}
>
<strong>{item.substr(0, query.length)}</strong>
{item.substr(query.length)}
</li>
);
QueryItem.propTypes = {
query: string.isRequired,
item: string.isRequired
};
const Typeahead = ({ list, classname }) => {
const inputRef = useRef();
const [query, setQuery] = useState("");
const [sortedList, setSortedList] = useState([]);
const [isListVisible, setIsListVisible] = useState(false);
useEffect(() => {
document.addEventListener("keydown", handleKeyDown, false);
document.addEventListener("click", handleDocumentClick, false);
return () => {
document.removeEventListener("keydown", handleKeyDown, false);
document.removeEventListener("click", handleDocumentClick, false);
};
});
useEffect(() => {
if (query && query.trim()) {
setSortedList(
list.filter(item => item.toLowerCase().startsWith(query.toLowerCase()))
);
} else {
setSortedList([]);
}
}, [query, list]);
const handleInputChange = query => {
setQuery(query);
setIsListVisible(true);
};
const handleItemClick = item => {
setQuery(item);
setIsListVisible(false);
};
const handleKeyDown = ({ key }, item) => {
if (key === "Escape") {
setIsListVisible(false);
}
if (key === "Enter" && item) {
setQuery(item, true);
setIsListVisible(false);
if (inputRef.current) {
inputRef.current.focus();
}
}
};
const handleDocumentClick = e => {
if (inputRef.current && e.target !== inputRef.current) {
setIsListVisible(false);
}
};
return (
<>
<input
value={query}
ref={inputRef}
onChange={({ target: { value } }) => handleInputChange(value)}
/>
{isListVisible && (
<ul className="list">
{sortedList.map(item => (
<QueryItem
key={item}
item={item}
query={query}
handleKeyDown={handleKeyDown}
handleItemClick={handleItemClick}
/>
))}
</ul>
)}
</>
);
};
Typeahead.propTypes = {
list: arrayOf(string).isRequired,
classname: string.isRequired
};
ReactDOM.render(
<Typeahead list={carBrands} classname="" />,
document.getElementById("root")
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment