惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

月光博客
月光博客
人人都是产品经理
人人都是产品经理
C
Check Point Blog
大猫的无限游戏
大猫的无限游戏
小众软件
小众软件
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
博客园 - 聂微东
Help Net Security
Help Net Security
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
L
LINUX DO - 最新话题
Cloudbric
Cloudbric
博客园 - Franky
阮一峰的网络日志
阮一峰的网络日志
O
OpenAI News
V2EX - 技术
V2EX - 技术
H
Hacker News: Front Page
Hacker News: Ask HN
Hacker News: Ask HN
Google DeepMind News
Google DeepMind News
PCI Perspectives
PCI Perspectives
爱范儿
爱范儿
Hacker News - Newest:
Hacker News - Newest: "LLM"
Martin Fowler
Martin Fowler
S
Schneier on Security
Webroot Blog
Webroot Blog
Know Your Adversary
Know Your Adversary
云风的 BLOG
云风的 BLOG
H
Help Net Security
I
InfoQ
Simon Willison's Weblog
Simon Willison's Weblog
雷峰网
雷峰网
J
Java Code Geeks
V
Visual Studio Blog
MyScale Blog
MyScale Blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
C
CERT Recently Published Vulnerability Notes
Google Online Security Blog
Google Online Security Blog
Spread Privacy
Spread Privacy
有赞技术团队
有赞技术团队
The GitHub Blog
The GitHub Blog
Engineering at Meta
Engineering at Meta
V
V2EX
Apple Machine Learning Research
Apple Machine Learning Research
www.infosecurity-magazine.com
www.infosecurity-magazine.com
A
About on SuperTechFans
S
SegmentFault 最新的问题
H
Hackread – Cybersecurity News, Data Breaches, AI and More
TaoSecurity Blog
TaoSecurity Blog
WordPress大学
WordPress大学
S
Securelist
P
Proofpoint News Feed

Kent C. Dodds Blog

Implementing Hybrid Semantic + Lexical Search Simplifying Containers with Cloudflare Sandboxes Migrating to Workspaces and Nx Offloading FFmpeg with Cloudflare Building Semantic Search on my Content Helping YOU ask ME questions with AI How I used Cursor to Migrate Frameworks The Dow's Start on the Covenant Path 2025 in Review The next chapter: EpicAI.pro AI is taking your job How I increased my visibility Launching Epic Web 2023 in Review Stop Being a Junior RSC with Dan Abramov and Joe Savona Live Stream Fixing a Memory Leak in a Production Node.js App 2022 in Review My Car Accident I Migrated from a Postgres Cluster to Distributed SQLite with LiteFS I'm building EpicWeb.dev A review of my time at Remix Remix: The Yang to React's Yin How I help you build better websites Why I Love Remix The State Initializer Pattern How to React ⚛️ Get a catch block error message with TypeScript Building an awesome image loading experience How Remix makes CSS clashes predictable Introducing the new kentcdodds.com How I built a modern website in 2021 How to use React Context effectively Static vs Unit vs Integration vs E2E Testing for Frontend Apps The Testing Trophy and Testing Classifications Array reduce vs chaining vs for loop Don't Solve Problems, Eliminate Them Super Simple Start to Remix Super Simple Start to ESModules in Node.js JavaScript Pass By Value Function Parameters How to write a Constrained Identity Function (CIF) in TypeScript How to optimize your context value How to write a React Component in TypeScript TypeScript Function Syntaxes Listify a JavaScript Array Build vs Buy: Component Libraries edition Using fetch with TypeScript Wrapping React.useState with TypeScript Define function overload types with TypeScript 2020 in Review Business and Engineering alignment Hi, thanks for reaching out to me 👋 useEffect vs useLayoutEffect Super simple start to Firebase functions Super simple start to Netlify functions Super Simple Start to css variables Favor Progress Over Pride in Open Source Testing Implementation Details How getting into Open Source has been awesome for me useState lazy initialization and function updates Use ternaries rather than && in JSX Application State Management with React Use react-error-boundary to handle errors in React JavaScript to Know for React How I structure Express apps What open source project should I contribute to? When I follow TDD AHA Programming 💡 How I Record Educational Videos Should I write a test or fix a bug? Stop mocking fetch Intentional Career Building Improve test error messages of your abstractions Tracing user interactions with React Eliminate an entire category of bugs with a few simple tools Common mistakes with React Testing Library Super Simple Start to React Stop using client-side route redirects The State Reducer Pattern with React Hooks Function forms Replace axios with a simple custom fetch wrapper How to test custom React hooks React Production Performance Monitoring Should I useState or useReducer? Stop using isLoading booleans Make Your Test Fail Make your own DevTools An Argument for Automation Fix the "not wrapped in act(...)" warning Super Simple Start to ESModules in the Browser Implementing a simple state machine library in JavaScript 2010s Decade in Review Why users care about how you write code Why I avoid nesting closures Don't call a React function component Why your team needs TestingJavaScript.com Inversion of Control Understanding React's key prop How to Enable React Concurrent Mode Profile a React App for Performance
Introducing downshift 🏎 for React ⚛️
2017-08-23 · via Kent C. Dodds Blog

downshift 🏎 is the primitive you need to build simple, flexible, WAI-ARIA compliant React autocomplete/typeahead/dropdown/select/combobox/etc (AKA "item selection") (p)react ⚛️ components. From PayPal 💙

One common component on the web is autocomplete:

Example of Google.com autocomplete/typeahead functionality

I've personally implemented 5 unique autocomplete abstractions in the last 4 years 😱 It's not an easy component to get right. And while you're busy doing all that work, don't forget to make it accessible! That's also a pretty big job.

Luckily, we have quite a few solutions for implementing an item selection like autocomplete. Specifically for React, there's react-select, react-autosuggest, react-autocomplete, and more. And now there's another one on the scene. It's called downshift, its emoji is the race car 🏎, and it's taking a different approach.

The state of item selection

The existing solutions for item selection inputs (autocomplete/typeahead/dropdown/select/combobox/etc) components in React will render the input and menu for you. Some of them allow you to specify what you'd like rendered, but will render in a predetermined location (or one that is configurable). Some of them also expose a list of class names that you can reference for styling the rendered component so you can make it fit your brand.

Unfortunately, this results in a wider API surface area and a more complicated implementation under the covers. And even with all of the flexibility provided, it can still be a real chore to make these things render what you want them to when, where, and how you want them to render.

These issues and limitations are largely due to the fact that these libraries are doing the rendering (React.createElement) for you.

How downshift 🏎 is different

There are two design decisions that provide distinguishing characteristics about downshift that sets it apart from existing solutions:

  1. The "Render Prop" pattern.
  2. The "Controlled Props" pattern.

Let's talk about how each of these works to bring you an incredible amount of value and flexibility.

render callback

There's actually not one instance of React.createElement (or JSX) anywhere in the downshift source code. Instead, downshift uses a render callback (following the render prop pattern). This allows you to render whatever you want inside <Downshift /> . It also puts you in total control of when, where, and how you render the selectable items in the menu.

This means that downshift doesn't need to expose nearly as many props as other solutions because there's no rendering to configure.

It turns out that rendering nothing increases simplicity, decreases bundle size, and enhances flexibility.

So what does it look like?

Here's a bare-bones autocomplete component with downshift:

import * as React from 'react'
import { render } from 'react-dom'
import Downshift from 'downshift'

const items = [
	{ value: 'apple' },
	{ value: 'pear' },
	{ value: 'orange' },
	{ value: 'grape' },
	{ value: 'banana' },
]

render(
	<Downshift
		onChange={(selection) => alert(`You selected ${selection.value}`)}
		itemToString={(item) => (item ? item.value : '')}
	>
		{({
			getInputProps,
			getItemProps,
			getLabelProps,
			getMenuProps,
			isOpen,
			inputValue,
			highlightedIndex,
			selectedItem,
		}) => (
			<div>
				<label {...getLabelProps()}>Enter a fruit</label>
				<input {...getInputProps()} />
				<ul {...getMenuProps()}>
					{isOpen
						? items
								.filter(
									(item) => !inputValue || item.value.includes(inputValue),
								)
								.map((item, index) => (
									<li
										{...getItemProps({
											key: item.value,
											index,
											item,
											style: {
												backgroundColor:
													highlightedIndex === index ? 'lightgray' : 'white',
												fontWeight: selectedItem === item ? 'bold' : 'normal',
											},
										})}
									>
										{item.value}
									</li>
								))
						: null}
				</ul>
			</div>
		)}
	</Downshift>,
	document.getElementById('root'),
)

gif showing the interaction

Error embedding https://codesandbox.io/s/6z67jvklw3

This is a pretty minimal example, and if you were to build similar minimal examples with other autocomplete libraries you could certainly do it in fewer lines of code. But what you'll notice here is that we're only passing onChange and render props to <Downshift />. The render prop is a function which is invoked with some helper methods and state for us to build our component out of. **downshift** is responsible for managing the user interaction, state, and most of accessibility for us, and we're responsible for rendering things based on that state.

Your autocomplete component is literally a function of the state of downshift

prop getters

Another thing you'll notice about the example above is the render function is passed more than just state. You're also given getInputProps and getItemProps. These are "prop getters" (inspired by Jared Forsyth) and they are the key to allowing you to render whatever you like. So long as you forward all the props to the appropriate element you're rendering (if you're rendering it at all), then downshift will do all the work of wiring things together.

Also, because downshift isn't rendering your menu or your items, downshift doesn't need to provide any APIs for how you filter or load them. You can load them asynchronously (example using Apollo and graph.cool and this example using Algolia Instantsearch) and you can control how you filter things (see this example that integrates geniejs watch me build geniejs integration here). This means that you don't need to learn or work around an API and can do it however works best for your use case.

This API also means that you don't have to render an input at all. So you can use downshift to implement a dropdown without any trouble too. Here's an example implementing a multi-select dropdown with downshift.

There are other prop getters available (some are there just to make accessibility easier). See the downshift docs for more info.

controlled props

The other design decision downshift has made is the use of controlled props.

If you've used React for a while, you've probably bumped into the concept of controlled and uncontrolled components. The most common of these is the <input /> component which allows you to specify a value prop if you want to control what the input value is. If you specify that prop, then you're responsible for keeping it up to date (often this requires an onChange handler to keep things in sync with when the user updates things).

downshift has this exact same concept for all pieces of state that it tracks: isOpen, selectedItem, inputValue, and highlightedIndex. This information is something that you have access to in your render function, as well as an onStateChange callback. But sometimes (just like with <input />) you need to be able to have complete control over it. So if you provide any of these as a prop to the downshift component (for example <Downshift isOpen={true} />), it becomes "controlled" and downshift will reference the value of your prop rather than track it internally with state.

Here's an example that controls the isOpen state:

Error embedding https://codesandbox.io/s/0mrl1819qw

This allows you to have complete control over the state of your component. Ryan Florence teaches about controllable components (like downshift) in this fantastic lesson (I highly recommend it). And you can watch me build the first iteration of the implementation here.

Accessibility

Accessibility (#a11y) is a really important feature, and quite frankly, it's not easy to get right for an item selection component like autocomplete. While developing it, I referenced several autocomplete components and Marcy Sutton was kind enough to give one of our examples an accessibility audit (Thank you Marcy!). Pull up an example with VoiceOver and I think you'll be impressed! We've worked hard to make sure that it's accessible (watch me work on it here), and based on my survey of the existing solutions, downshift is the most accessible component of its kind.

Size

downshift is quite a bit smaller than other similar solutions. The UMD build rolls in at 14.34kb (uncompressed). Because downshift gives you full control over rendering, there's much less code required. In addition, it was easy to make work out of the box with preact ⚛️ (a teeny tiny version of react. No need to add preact-compat). I was able to create an experiment using preact-habitat that gives a completely frameworkless implementation of autocomplete in less than 26kb (uncompressed). That size includes downshift + preact + preact-habitat. You have my permission to jump for joy 😉. Shout out to Jason Miller and Zouhir for preact and preact-habitat!

Where is it?

I built downshift for this country selector experience on PayPal:

gif of a country selector experience on PayPal

We also use the same component in the recipient selector:

gif of a recipient selector experience on PayPal

You can watch me build part of these PayPal experiences here.

We actually have several other item selection experiences in our app that have slightly different use cases and necessitated us having multiple implementations of an autocomplete component in the same app! So that's why I build downshift: so we could have a single implementation that was flexible enough to cover all the use cases. The experience in PayPal should ship next week.

There are other teams within PayPal who are integrating downshift with their applications right now as well.

I started working on downshift about a month ago, the first beta was published (as react-autocompletely) the next day. It's slowly been gaining popularity (it already has 900 🌟 and 7k downloads/month) even before the official 1.0.0 release! So it's definitely being used in several places, but the first production deployment that I'm aware of is in codesandbox from Ives van Hoorne (gif from his tweet):

The codesandbox.io file chooser which you press ⌘+P to open

Fun fact: both of these solutions are also using match-sorter, which is a user friendly best-match sorting library. I highly recommend it!

What's next

downshift is a pretty minimal component by design. It does take a little bit more work to get things that you get out of the box from other solutions. But I'm convinced that you can build all of those features on top of downshift. So I'm hoping that the community can help build downshift-powered versions of some of these features and get those published to npm. Here's a great place to get started!

Conclusion

I need to give a big shoutout to Ryan Florence. His lesson on "Compound Components" got me started on this. downshift doesn't actually use compound components anymore, but the first incarnation did! So thanks to Ryan! And you can watch me develop most of downshift on this YouTube playlist (starting with this first video) 📺.

And again, a big shoutout to Jared Forsyth for inspiring the prop getters one day when we just bumped into each other at the airport ✈️ That made a big difference in the direction of the API as well.

Special shoutout to Travis Arnold, Julien Goux, the_Simian, and all the contributors (so far) for their help with forming the downshift API into what it is now.

Please give downshift a star 🌟, a watch 👀, and a try 😎.