UNPKG

27.4 kBMarkdownView Raw
1<h1 align="center">
2 downshift 🏎
3 <br>
4 <img src="https://cdn.rawgit.com/paypal/downshift/d9e94139/other/logo/downshift.svg" alt="downshift logo" title="downshift logo" width="300">
5 <br>
6</h1>
7<p align="center" style="font-size: 1.2rem;">Primitives to build simple, flexible, WAI-ARIA compliant React
8autocomplete/dropdown/select/combobox components</p>
9
10<hr />
11
12[![Build Status][build-badge]][build]
13[![Code Coverage][coverage-badge]][coverage]
14[![downloads][downloads-badge]][npmcharts]
15[![version][version-badge]][package]
16[![MIT License][license-badge]][LICENSE]
17
18[![All Contributors](https://img.shields.io/badge/all_contributors-18-orange.svg?style=flat-square)](#contributors)
19[![PRs Welcome][prs-badge]][prs]
20[![Chat][chat-badge]][chat]
21[![Code of Conduct][coc-badge]][coc]
22
23[![Supports React and Preact][react-badge]][react]
24[![size][size-badge]][unpkg-dist]
25[![gzip size][gzip-badge]][unpkg-dist]
26[![module formats: umd, cjs, and es][module-formats-badge]][unpkg-dist]
27
28[![Watch on GitHub][github-watch-badge]][github-watch]
29[![Star on GitHub][github-star-badge]][github-star]
30[![Tweet][twitter-badge]][twitter]
31
32## The problem
33
34You need an autocomplete/dropdown/select experience in your application and you
35want it to be accessible. You also want it to be simple and flexible to account
36for your use cases.
37
38## This solution
39
40This is a component that controls user interactions and state for you so you can
41create autocomplete/dropdown/select/etc. components. It's based on ideas from
42the talk ["Compound Components"][compound-components-talk] which effectively
43gives you maximum flexibility with a minimal API because you are responsible
44for the rendering of everything and you simply apply props to what you're
45rendering.
46
47This differs from other solutions which render things for their use case and
48then expose many options to allow for extensibility causing an API that is less
49easy to use and less flexible as well as making the implementation more
50complicated and harder to contribute to.
51
52> NOTE: The original use case of this component is autocomplete, however the API
53> is powerful and flexible enough to build things like dropdowns as well.
54
55## Installation
56
57**This component is currently released as a Release Candidate. It is quite stable, but not officially released yet.**
58
59This module is distributed via [npm][npm] which is bundled with [node][node] and
60should be installed as one of your project's `dependencies`:
61
62```
63npm install --save downshift@rc
64```
65
66> This package also depends on `react` and `prop-types`. Please make sure you
67> have those installed as well.
68
69> Note also this library supports `preact` out of the box. If you are using
70> `preact` then look in the `dist/` folder and use the module you want with the
71> `preact` suffix.
72
73## Usage
74
75```jsx
76import Downshift from 'downshift'
77
78function BasicAutocomplete({items, onChange}) {
79 return (
80 <Downshift onChange={onChange}>
81 {({
82 getInputProps,
83 getItemProps,
84 isOpen,
85 inputValue,
86 selectedItem,
87 highlightedIndex
88 }) => (
89 <div>
90 <input {...getInputProps({placeholder: 'Favorite color ?'})} />
91 {isOpen ? (
92 <div style={{border: '1px solid #ccc'}}>
93 {items
94 .filter(
95 i =>
96 !inputValue ||
97 i.toLowerCase().includes(inputValue.toLowerCase()),
98 )
99 .map((item, index) => (
100 <div
101 {...getItemProps({item, index})}
102 key={item}
103 style={{
104 backgroundColor:
105 highlightedIndex === index ? 'gray' : 'white',
106 fontWeight: selectedItem === item ? 'bold' : 'normal',
107 }}
108 >
109 {item}
110 </div>
111 ))}
112 </div>
113 ) : null}
114 </div>
115 )}
116 </Downshift>
117 )
118}
119
120function App() {
121 return (
122 <BasicAutocomplete
123 items={['apple', 'orange', 'carrot']}
124 onChange={selectedItem => console.log(selectedItem)}
125 />
126 )
127}
128```
129
130`downshift` is the only component. It doesn't render anything itself, it just
131calls the child function and renders that. Wrap everything in
132`<Downshift>{/* your function here! */}</Downshift>`.
133
134## Props:
135
136### defaultSelectedItem
137
138> `any` | defaults to `null`
139
140Pass an item or an array of items that should be selected by default.
141
142### defaultHighlightedIndex
143
144> `number`/`null` | defaults to `null`
145
146This is the initial index to highlight when the menu first opens.
147
148### defaultInputValue
149
150> `string` | defaults to `''`
151
152This is the initial input value.
153
154### defaultIsOpen
155
156> `boolean` | defaults to `false`
157
158This is the initial `isOpen` value.
159
160### itemToString
161
162> `function(item: any)` | defaults to: `i => (i == null ? '' : String(i))`
163
164Used to determine the string value for the selected item (which is used to
165compute the `inputValue`.
166
167### getA11yStatusMessage
168
169> `function({/* see below */})` | default messages provided in English
170
171This function is passed as props to a `Status` component nested within and
172allows you to create your own assertive ARIA statuses.
173
174A default `getA11yStatusMessage` function is provided that will check
175`resultCount` and return "No results." or if there are results but no item is
176highlighted, "`resultCount` results are available, use up and down arrow keys
177to navigate." If an item is highlighted it will run
178`itemToString(highlightedItem)` and display the value of the `highlightedItem`.
179
180The object you are passed to generate your status message has the following
181properties:
182
183<!-- This table was generated via http://www.tablesgenerator.com/markdown_tables -->
184
185| property | type | description |
186|-----------------------|-----------------|----------------------------------------------------------------------------------------------|
187| `highlightedIndex` | `number`/`null` | The currently highlighted index |
188| `highlightedValue` | `any` | The value of the highlighted item |
189| `inputValue` | `string` | The current input value |
190| `isOpen` | `boolean` | The `isOpen` state |
191| `itemToString` | `function(any)` | The `itemToString` function (see props) for getting the string value from one of the options |
192| `previousResultCount` | `number` | The total items showing in the dropdown the last time the status was updated |
193| `resultCount` | `number` | The total items showing in the dropdown |
194| `selectedItem` | `any` | The value of the currently selected item |
195
196### onChange
197
198> `function(selectedItem: any, allState: object)` | optional, no useful default
199
200Called when the user selects an item. Called with the item that was selected
201and the new state of `downshift`. (see `onStateChange` for more info on
202`allState`).
203
204### onStateChange
205
206> `function(changes: object, allState: object)` | optional, no useful default
207
208This function is called anytime the internal state changes. This can be useful
209if you're using downshift as a "controlled" component, where you manage some or
210all of the state (e.g. isOpen, selectedItem, highlightedIndex, etc) and then
211pass it as props, rather than letting downshift control all its state itself.
212The parameters both take the shape of internal state
213(`{highlightedIndex: number, inputValue: string, isOpen: boolean, selectedItem: any}`)
214but differ slightly.
215
216- `changes`: These are the properties that actually have changed since the last
217 state change
218- `allState`: This is the full state object of all the state in your `downshift`
219 component.
220
221### itemCount
222
223> `number` | optional, defaults the number of times you call getItemProps
224
225This is useful if you're using some kind of virtual listing component for
226"windowing" (like [`react-virtualized`](https://github.com/bvaughn/react-virtualized)).
227
228### highlightedIndex
229
230> `number` | **control prop** (read more about this in the "Control Props" section below)
231
232The index that should be highlighted
233
234### inputValue
235
236> `string` | **control prop** (read more about this in the "Control Props" section below)
237
238The value the input should have
239
240### isOpen
241
242> `boolean` | **control prop** (read more about this in the "Control Props" section below)
243
244Whether the menu should be considered open or closed. Some aspects of the
245downshift component respond differently based on this value (for example, if
246`isOpen` is true when the user hits "Enter" on the input field, then the
247item at the `highlightedIndex` item is selected).
248
249### `selectedItem`
250
251> `any`/`Array(any)` | **control prop** (read more about this in the "Control Props" section below)
252
253The currently selected item.
254
255### children
256
257> `function({})` | *required*
258
259This is called with an object. Read more about the properties of this object
260in the section "Child Callback Function"
261
262## Control Props
263
264downshift manages its own state internally and calls your `onChange` and
265`onStateChange` handlers with any relevant changes. The state that downshift
266manages includes: `isOpen`, `selectedItem`, `inputValue`, and
267`highlightedIndex`. Your child callback function (read more below) can be used
268to manipulate this state from within the render function and can likely support
269many of your use cases.
270
271However, if more control is needed, you can pass any of these pieces of state as
272a prop (as indicated above) and that state becomes controlled. As soon as
273`this.props[statePropKey] !== undefined`, internally, `downshift` will determine
274its state based on your prop's value rather than its own internal state. You
275will be required to keep the state up to date (this is where `onStateChange`
276comes in really handy), but you can also control the state from anywhere, be
277that state from other components, `redux` (example wanted!), `react-router`
278(example wanted!), or anywhere else.
279
280## Child Callback Function
281
282This is where you render whatever you want to based on the state of `downshift`.
283The function is passed as the child prop:
284`<Downshift>{/* right here*/}</Downshift>`
285
286The properties of this object can be split into three categories as indicated
287below:
288
289### prop getters
290
291These functions are used to apply props to the elements that you render.
292This gives you maximum flexibility to render what, when, and wherever you like.
293You call these on the element in question (for example:
294`<input {...getInputProps()}`)). It's advisable to pass all your props to that
295function rather than applying them on the element yourself to avoid your props
296being overridden (or overriding the props returned). For example:
297`getInputProps({onKeyUp(event) {console.log(event)}})`.
298
299<!-- This table was generated via http://www.tablesgenerator.com/markdown_tables -->
300
301| property | type | description |
302|------------------|----------------|---------------------------------------------------------------------------------------------|
303| `getButtonProps` | `function({})` | returns the props you should apply to any menu toggle button element you render. |
304| `getInputProps` | `function({})` | returns the props you should apply to the `input` element that you render. |
305| `getItemProps` | `function({})` | returns the props you should apply to any menu item elements you render. |
306| `getLabelProps` | `function({})` | returns the props you should apply to the `label` element that you render. |
307| `getRootProps` | `function({})` | returns the props you should apply to the root element that you render. It can be optional. |
308
309#### `getRootProps`
310
311Most of the time, you can just render a `div` yourself and `Downshift` will
312apply the props it needs to do its job (and you don't need to call this
313function). However, if you're rendering a composite component (custom component)
314as the root element, then you'll need to call `getRootProps` and apply that to
315your root element.
316
317Required properties:
318
319- `refKey`: if you're rendering a composite component, that component will need
320 to accept a prop which it forwards to the root DOM element. Commonly, folks
321 call this `innerRef`. So you'd call: `getRootProps({refKey: 'innerRef'})`
322 and your composite component would forward like: `<div ref={props.innerRef} />`
323
324#### `getInputProps`
325
326This method should be applied to the `input` you render. It is recommended that
327you pass all props as an object to this method which will compose together any
328of the event handlers you need to apply to the `input` while preserving the
329ones that `downshift` needs to apply to make the `input` behave.
330
331There are no required properties for this method.
332
333#### `getLabelProps`
334
335This method should be applied to the `label` you render. It is useful for
336ensuring that the `for` attribute on the `<label>` (`htmlFor` as a react prop)
337is the same as the `id` that appears on the `input`. If no `htmlFor` is provided
338then an ID will be generated and used for the `input` and the `label` `for`
339attribute.
340
341There are no required properties for this method.
342
343> Note: You can definitely get by without using this (just provide an `id` to
344> your input and the same `htmlFor` to your `label` and you'll be good with
345> accessibility). However, we include this so you don't forget and it makes
346> things a little nicer for you. You're welcome 😀
347
348#### `getItemProps`
349
350This method should be applied to any menu items you render. You pass it an object
351and that object must contain `index` (number) and `item` (anything) properties.
352
353Required properties:
354
355- `index`: this is how `downshift` keeps track of your item when
356 updating the `highlightedIndex` as the user keys around.
357- `item`: this is the item data that will be selected when the user selects a
358 particular item.
359
360#### `getButtonProps`
361
362Call this and apply the returned props to a `button`. It allows you to toggle
363the `Menu` component. You can definitely build something like this yourself
364(all of the available APIs are exposed to you), but this is nice because it
365will also apply all of the proper ARIA attributes. The `aria-label` prop is in
366English. You should probably override this yourself so you can provide
367translations:
368
369```jsx
370<button {...getButtonProps({
371 'aria-label': translateWithId(isOpen ? 'close.menu' : 'open.menu'),
372})} />
373```
374
375### actions
376
377These are functions you can call to change the state of the downshift component.
378
379<!-- This table was generated via http://www.tablesgenerator.com/markdown_tables -->
380
381| property | type | description |
382|-------------------------|----------------------------|------------------------------------------------------------------------------------------------------------------|
383| `clearSelection` | `function()` | clears the selection |
384| `closeMenu` | `function()` | closes the menu |
385| `openMenu` | `function()` | opens the menu |
386| `selectHighlightedItem` | `function()` | selects the item that is currently highlighted |
387| `selectItem` | `function(item: any)` | selects the given item |
388| `selectItemAtIndex` | `function(index: number)` | selects the item at the given index |
389| `setHighlightedIndex` | `function(index: number)` | call to set a new highlighted index |
390| `toggleMenu` | `function(state: boolean)` | toggle the menu open state (if `state` is not provided, then it will be set to the inverse of the current state) |
391
392### state
393
394These are values that represent the current state of the downshift component.
395
396<!-- This table was generated via http://www.tablesgenerator.com/markdown_tables -->
397
398| property | type | description |
399|--------------------|-------------------|------------------------------------------------|
400| `highlightedIndex` | `number` / `null` | the currently highlighted item |
401| `inputValue` | `string` / `null` | the current value of the `getInputProps` input |
402| `isOpen` | `boolean` | the menu open state |
403| `selectedItem` | `any` | the currently selected item input |
404
405## Examples
406
407Examples exist on [codesandbox.io][examples]:
408
409- [multiple selection example](https://codesandbox.io/s/W6gyJ30kn) (uses controlled `selectedItem` API).
410- [downshift Apollo example](https://codesandbox.io/s/j2omZpK3W)
411- [downshift Spectre.css example](https://codesandbox.io/s/M89KQOBRB)
412- [Material UI (1.0.0-beta.4) Combobox Using Downshift](https://codesandbox.io/s/QMGq4kAY)
413- [Integration with `GenieJS`](https://codesandbox.io/s/jRLKrxwgl) ([learn more about `genie` here](https://github.com/kentcdodds/genie))
414- [Handling and displaying errors](https://codesandbox.io/s/zKE37vorr)
415
416If you would like to add an example, follow these steps:
417
4181. Fork [this codesandbox](http://kcd.im/ds-example)
4192. Make sure your version (under dependencies) is the latest available version.
4203. Update the title and description
4214. Update the code for your example (add some form of documentation to explain what it is)
4225. Add the tag: `downshift:example`
423
424## Inspiration
425
426I was heavily inspired by [Ryan Florence][ryan] and his talk entitled:
427["Compound Components"][compound-components-talk]. I also took a few ideas from
428the code in [`react-autocomplete`][react-autocomplete] and
429[jQuery UI's Autocomplete][jquery-complete].
430
431The `getXProps` functions were inspired by [Jared Forsyth][jared]. That bit of
432inspiration made a big impact on the flexibility and simplicity of this API.
433
434You can watch me build the first iteration of `downshift` on YouTube:
435
436- [Part 1](https://www.youtube.com/watch?v=2kzD1IjDy5s&list=PLV5CVI1eNcJh5CTgArGVwANebCrAh2OUE&index=11)
437- [Part 2](https://www.youtube.com/watch?v=w1Z7Jvj08_s&list=PLV5CVI1eNcJh5CTgArGVwANebCrAh2OUE&index=10)
438
439You'll find more recordings of me working on `downshift` on [my livestream YouTube playlist][yt-playlist].
440
441## Other Solutions
442
443You can implement these other solutions using `downshift`, but if
444you'd prefer to use these out of the box solutions, then that's fine too:
445
446- [`react-select`](https://github.com/JedWatson/react-select)
447- [`react-autocomplete`](https://github.com/reactjs/react-autocomplete)
448
449## Contributors
450
451Thanks goes to these people ([emoji key][emojis]):
452
453<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
454| [<img src="https://avatars.githubusercontent.com/u/1500684?v=3" width="100px;"/><br /><sub>Kent C. Dodds</sub>](https://kentcdodds.com)<br />[💻](https://github.com/paypal/downshift/commits?author=kentcdodds "Code") [📖](https://github.com/paypal/downshift/commits?author=kentcdodds "Documentation") [🚇](#infra-kentcdodds "Infrastructure (Hosting, Build-Tools, etc)") [⚠️](https://github.com/paypal/downshift/commits?author=kentcdodds "Tests") | [<img src="https://avatars0.githubusercontent.com/u/100200?v=4" width="100px;"/><br /><sub>Ryan Florence</sub>](http://twitter.com/ryanflorence)<br />[🤔](#ideas-ryanflorence "Ideas, Planning, & Feedback") | [<img src="https://avatars3.githubusercontent.com/u/112170?v=4" width="100px;"/><br /><sub>Jared Forsyth</sub>](http://jaredforsyth.com)<br />[🤔](#ideas-jaredly "Ideas, Planning, & Feedback") [📖](https://github.com/paypal/downshift/commits?author=jaredly "Documentation") | [<img src="https://avatars1.githubusercontent.com/u/8162598?v=4" width="100px;"/><br /><sub>Jack Moore</sub>](https://github.com/jtmthf)<br />[💡](#example-jtmthf "Examples") | [<img src="https://avatars1.githubusercontent.com/u/2762082?v=4" width="100px;"/><br /><sub>Travis Arnold</sub>](http://travisrayarnold.com)<br />[💻](https://github.com/paypal/downshift/commits?author=souporserious "Code") [📖](https://github.com/paypal/downshift/commits?author=souporserious "Documentation") | [<img src="https://avatars2.githubusercontent.com/u/244704?v=4" width="100px;"/><br /><sub>Jeremy Gayed</sub>](http://www.jeremygayed.com)<br />[💡](#example-tizmagik "Examples") | [<img src="https://avatars3.githubusercontent.com/u/6270048?v=4" width="100px;"/><br /><sub>Haroen Viaene</sub>](https://haroen.me)<br />[💡](#example-Haroenv "Examples") |
455| :---: | :---: | :---: | :---: | :---: | :---: | :---: |
456| [<img src="https://avatars2.githubusercontent.com/u/15073300?v=4" width="100px;"/><br /><sub>monssef</sub>](https://github.com/rezof)<br />[💡](#example-rezof "Examples") | [<img src="https://avatars2.githubusercontent.com/u/5382443?v=4" width="100px;"/><br /><sub>Federico Zivolo</sub>](https://fezvrasta.github.io)<br />[📖](https://github.com/paypal/downshift/commits?author=FezVrasta "Documentation") | [<img src="https://avatars3.githubusercontent.com/u/746482?v=4" width="100px;"/><br /><sub>Divyendu Singh</sub>](https://divyendusingh.com)<br />[💡](#example-divyenduz "Examples") | [<img src="https://avatars1.githubusercontent.com/u/841955?v=4" width="100px;"/><br /><sub>Muhammad Salman</sub>](https://github.com/salmanmanekia)<br />[💻](https://github.com/paypal/downshift/commits?author=salmanmanekia "Code") | [<img src="https://avatars3.githubusercontent.com/u/10820159?v=4" width="100px;"/><br /><sub>João Alberto</sub>](https://twitter.com/psicotropidev)<br />[💻](https://github.com/paypal/downshift/commits?author=psicotropicos "Code") | [<img src="https://avatars0.githubusercontent.com/u/16327281?v=4" width="100px;"/><br /><sub>Bernard Lin</sub>](https://github.com/bernard-lin)<br />[💻](https://github.com/paypal/downshift/commits?author=bernard-lin "Code") [📖](https://github.com/paypal/downshift/commits?author=bernard-lin "Documentation") | [<img src="https://avatars1.githubusercontent.com/u/7330124?v=4" width="100px;"/><br /><sub>Geoff Davis</sub>](https://geoffdavis.info)<br />[💡](#example-geoffdavis92 "Examples") |
457| [<img src="https://avatars0.githubusercontent.com/u/3415488?v=4" width="100px;"/><br /><sub>Anup</sub>](https://github.com/reznord)<br />[📖](https://github.com/paypal/downshift/commits?author=reznord "Documentation") | [<img src="https://avatars0.githubusercontent.com/u/340520?v=4" width="100px;"/><br /><sub>Ferdinand Salis</sub>](http://ferdinandsalis.com)<br />[🐛](https://github.com/paypal/downshift/issues?q=author%3Aferdinandsalis "Bug reports") [💻](https://github.com/paypal/downshift/commits?author=ferdinandsalis "Code") | [<img src="https://avatars2.githubusercontent.com/u/662750?v=4" width="100px;"/><br /><sub>Kye Hohenberger</sub>](https://github.com/tkh44)<br />[🐛](https://github.com/paypal/downshift/issues?q=author%3Atkh44 "Bug reports") | [<img src="https://avatars0.githubusercontent.com/u/1443499?v=4" width="100px;"/><br /><sub>Julien Goux</sub>](https://github.com/jgoux)<br />[🐛](https://github.com/paypal/downshift/issues?q=author%3Ajgoux "Bug reports") [💻](https://github.com/paypal/downshift/commits?author=jgoux "Code") [⚠️](https://github.com/paypal/downshift/commits?author=jgoux "Tests") |
458<!-- ALL-CONTRIBUTORS-LIST:END -->
459
460This project follows the [all-contributors][all-contributors] specification.
461Contributions of any kind welcome!
462
463## LICENSE
464
465MIT
466
467[npm]: https://www.npmjs.com/
468[node]: https://nodejs.org
469[build-badge]: https://img.shields.io/travis/paypal/downshift.svg?style=flat-square
470[build]: https://travis-ci.org/paypal/downshift
471[coverage-badge]: https://img.shields.io/codecov/c/github/paypal/downshift.svg?style=flat-square
472[coverage]: https://codecov.io/github/paypal/downshift
473[version-badge]: https://img.shields.io/npm/v/downshift.svg?style=flat-square
474[package]: https://www.npmjs.com/package/downshift
475[downloads-badge]: https://img.shields.io/npm/dm/downshift.svg?style=flat-square
476[npmcharts]: http://npmcharts.com/compare/downshift
477[license-badge]: https://img.shields.io/npm/l/downshift.svg?style=flat-square
478[license]: https://github.com/paypal/downshift/blob/master/LICENSE
479[prs-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square
480[prs]: http://makeapullrequest.com
481[chat]: https://gitter.im/paypal/downshift
482[chat-badge]: https://img.shields.io/gitter/room/paypal/downshift.svg?style=flat-square
483[coc-badge]: https://img.shields.io/badge/code%20of-conduct-ff69b4.svg?style=flat-square
484[coc]: https://github.com/paypal/downshift/blob/master/other/CODE_OF_CONDUCT.md
485[react-badge]: https://img.shields.io/badge/%E2%9A%9B%EF%B8%8F-(p)react-00d8ff.svg?style=flat-square
486[react]: https://facebook.github.io/react/
487[gzip-badge]: http://img.badgesize.io/https://unpkg.com/downshift/dist/downshift.umd.min.js?compression=gzip&label=gzip%20size&style=flat-square
488[size-badge]: http://img.badgesize.io/https://unpkg.com/downshift/dist/downshift.umd.min.js?label=size&style=flat-square
489[unpkg-dist]: https://unpkg.com/downshift/dist/
490[module-formats-badge]: https://img.shields.io/badge/module%20formats-umd%2C%20cjs%2C%20es-green.svg?style=flat-square
491[github-watch-badge]: https://img.shields.io/github/watchers/paypal/downshift.svg?style=social
492[github-watch]: https://github.com/paypal/downshift/watchers
493[github-star-badge]: https://img.shields.io/github/stars/paypal/downshift.svg?style=social
494[github-star]: https://github.com/paypal/downshift/stargazers
495[twitter]: https://twitter.com/intent/tweet?text=Check%20out%20downshift!%20https://github.com/paypal/downshift%20%F0%9F%91%8D
496[twitter-badge]: https://img.shields.io/twitter/url/https/github.com/paypal/downshift.svg?style=social
497[emojis]: https://github.com/kentcdodds/all-contributors#emoji-key
498[all-contributors]: https://github.com/kentcdodds/all-contributors
499[ryan]: https://github.com/ryanflorence
500[compound-components-talk]: https://www.youtube.com/watch?v=hEGg-3pIHlE
501[react-autocomplete]: https://www.npmjs.com/package/react-autocomplete
502[jquery-complete]: https://jqueryui.com/autocomplete/
503[examples]: https://codesandbox.io/search?refinementList%5Btags%5D%5B0%5D=downshift%3Aexample&page=1
504[yt-playlist]: https://www.youtube.com/playlist?list=PLV5CVI1eNcJh5CTgArGVwANebCrAh2OUE
505[jared]: https://github.com/jaredly