一般的なドロップダウンのユースケースでダウンシフトを使用する方法
序章
Downshift は、シンプルで柔軟性のある、WAI-ARIA準拠の拡張入力Reactコンポーネントの構築を支援するライブラリです。 その主なユースケースはオートコンプリートコンポーネントの構築ですが、ドロップダウンコンポーネントの構築にも使用できます。
このチュートリアルでは、ダウンシフトで解決されるいくつかの一般的なユースケースについて説明します。
前提条件
このチュートリアルに従うには、マシンにNodeとNPMがインストールされている必要があります。 Reactの基本的な理解は、このチュートリアルを最大限に活用するのに役立ちます。
Node.jsがインストールされていない場合は、 Node Webサイトにアクセスして、オペレーティングシステムに推奨されるバージョンをインストールしてください。
ステップ1—アプリケーションのセットアップ
Create React App
を利用して、ビルド構成のないシンプルなReactアプリを作成します。 Create React App
がインストールされていない場合は、端末でnpm i create-react-app
を実行してインストールしてください。 マシンにインストールしたら、次のコマンドを実行してdownshift-examples
というディレクトリに新しいReactプロジェクトをセットアップし、次のコマンドを実行してこの新しいディレクトリに移動します。
- create-react-app downshift-examples
- cd downshift-examples
downshift-examples
ディレクトリに移動したら、次のコマンドを実行して、Downshiftおよびその他のパッケージをインストールします。
- yarn add downshift axios react-popper
src
フォルダーにあるApp.css
ファイルを開き、次のスタイルを追加します。
input {
margin: 1rem;
width: 20rem;
padding: 1rem .5rem;
}
.downshift-dropdown {
margin: 0 auto;
width: 20rem;
border: 1px solid whitesmoke;
border-bottom: none;
}
.dropdown-item {
padding: 0.5rem;
cursor: pointer;
border-bottom: 1px solid whitesmoke;
font-size: 1rem;
text-align: left;
}
.dropdown-button {
padding: 0.6rem;
border-radius: 3px;
background: white;
cursor: pointer;
}
.popper-div {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
margin-top: 5rem;
}
.popper-item {
padding: .5rem;
border-bottom: 1px solid whitesmoke;
}
すべてがセットアップされたら、いくつかの基本的なダウンシフトの概念を見てみましょう。
ダウンシフトの概念の概要
ダウンシフトを使用する場合、必要なコンポーネントは<Downshift />
のみです。 <Downshift />
コンポーネントと呼び、いくつかの小道具を渡すと、魔法のように機能します。 最もよく使われる小道具のいくつかを次に示します。
onChange
:この関数は、ユーザーがアイテムを選択し、選択したアイテムが変更されたときに呼び出されます。selectedItem
を返します。itemToString
:この関数は、inputValue
の計算に使用される選択されたアイテムの文字列値を決定するために使用されます。inputValue
:これは入力フィールドが持つべき値を表します。getInputProps
:この関数は、レンダリングするinput
要素に適用する必要のある小道具を返します。getItemProps
:この関数は、レンダリングするメニュー項目要素に適用する必要のある小道具を返します。isOpen
:これはメニューが開いているかどうかを示すブール値です。selectedItem
:これは現在選択されているアイテム入力を表します。render
:これは、ダウンシフトの状態に基づいて、必要なものをレンダリングする場所です。 この関数はオブジェクトとともに呼び出されます。
小道具の完全なリストについては、ドキュメントを確認できます。 それでは、この知識を活用しましょう。
ステップ2—選択フィールドの作成
最初のダウンシフトのユースケースは、選択フィールドです。 先に進み、アプリのルートディレクトリのsrc
フォルダーにDownshiftOne.js
ファイルを作成します。 次のコードを追加します。
import React from 'react'
import Downshift from 'downshift';
const books = [
{ name: 'Harry Potter' },
{ name: 'Net Moves' },
{ name: 'Half of a yellow sun' },
{ name: 'The Da Vinci Code' },
{ name: 'Born a crime' },
];
const onChange = (selectedBook) => {
alert(`your favourite book is ${selectedBook.name}`)
}
export default () => {
return (
<Downshift onChange={onChange} itemToString={books => (books ? books.name : '')}>
{/* we'll insert a callback here */}
</DownShift>
)
}
上記のコードでは、ReactとDownshiftをインポートし、本の配列、onChange
関数、および<Downshift/>
コンポーネントを返す関数コンポーネントを宣言します。 <Downshift/>
コンポーネントでは、onChange
およびitemToString
プロップを渡します。 <Downshift/>
コンポーネント内で、他の小道具をコールバックに渡し、入力フィールドをレンダリングします。
次に、コールバックで必要な小道具を<Downshift/>
コンポーネントに渡します。 機能コンポーネントを次のように更新します。
...
export default () => {
return (
<Downshift onChange={onChange} itemToString={books => (books ? books.name : '')}>
// pass the downshift props into a callback
{({ getInputProps, getItemProps, isOpen, inputValue, highlightedIndex, selectedItem, getLabelProps }) => (
<div>
// add a label tag and pass our label text to the getLabelProps function
<label style={{ marginTop: '1rem', display: 'block' }} {...getLabelProps()}>Choose your favourite book</label> <br />
// add our input element and pass our placeholder to the getInputProps function
<input {...getInputProps({ placeholder: "Search books" })} />
// if the input element is open, render the div else render nothing
{isOpen ? (
<div className="downshift-dropdown">
{
// filter the books and return items that match the inputValue
books
.filter(item => !inputValue || item.name.toLowerCase().includes(inputValue.toLowerCase()))
// map the return value and return a div
.map((item, index) => (
<div
className="dropdown-item"
{...getItemProps({ key: item.name, index, item })}
style={{
backgroundColor: highlightedIndex === index ? 'lightgray' : 'white',
fontWeight: selectedItem === item ? 'bold' : 'normal',
}}>
{item.name}
</div>
))
}
</div>
) : null}
</div>
)}
</Downshift>
)
}
上記のコードスニペットでは、ダウンシフトプロップをパラメーターとしてコールバックに渡しました。
{({ getInputProps, getItemProps, isOpen, inputValue, highlightedIndex, selectedItem, getLabelProps }) => ()}
コールバックでは、入力タグを追加し、それにgetInputProps
小道具を渡します。
<input {...getInputProps({ placeholder: "Search books" })} />
次に、入力要素が開いているかどうかを確認します。 そうである場合は、メニューを含むdiv
要素を返し、そうでない場合はnull
を返します。
{ isOpen ? (<div className="downshift-dropdown">...</div>) : null }
最後に、返すdiv
要素で、books配列をフィルタリングして、inputValue
を含むアイテムのみを返します。 次に、フィルタリングされた本をマッピングして、ページにレンダリングします。
また、getItemProps
関数をmap
関数でレンダリングされたdiv
に渡しました。 アイテムのレンダリング中に適用した小道具を返します。
コンポーネントを親App
コンポーネントにインポートして、機能選択フィールドを確認してみましょう。
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import DownshiftOne from './DownshiftOne'; // import the component
class App extends Component {
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to React</h1>
</header>
<DownshiftOne /> // render the component
</div>
);
}
}
export default App;
ターミナルでnpm start
を実行して、サーバーが実行されていることを確認します。 ブラウザでhttp://localhost:3000
を開くと、アプリが実行されていることがわかります。
ステップ3—Axiosでダウンシフトを使用する
次の例では、Downshiftを使用して映画の検索フィールドを作成します。 src
フォルダーに、DownshiftTwo.js
ファイルを作成し、次のコードを追加します。
import React, { Component } from 'react'
import Downshift from 'downshift';
import axios from 'axios';
export default class DownshiftTwo extends Component {
constructor(props) {
super(props)
this.state = {
movies: []
}
this.fetchMovies = this.fetchMovies.bind(this)
this.inputOnChange = this.inputOnChange.bind(this)
}
// onChange method for the input field
inputOnChange(event) {
if (!event.target.value) {
return
}
this.fetchMovies(event.target.value)
}
// input field for the <Downshift /> component
downshiftOnChange(selectedMovie) {
alert(`your favourite movie is ${selectedMovie.title}`)
}
// method to fetch the movies from the movies API
fetchMovies(movie) {
const moviesURL = `https://api.themoviedb.org/3/search/movie?api_key=APIKey&query=${movie}`;
axios.get(moviesURL).then(response => {
this.setState({ movies: response.data.results })
})
}
render() {
return (
<Downshift onChange={this.downshiftOnChange} itemToString={item => (item ? item.title : '')}>
// pass the downshift props into a callback
{({ selectedItem, getInputProps, getItemProps, highlightedIndex, isOpen, inputValue, getLabelProps }) => (
<div>
// add a label tag and pass our label text to the getLabelProps function
<label style={{ marginTop: '1rem', display: 'block' }} {...getLabelProps()}>Choose your favourite movie</label> <br />
// add a input tag and pass our placeholder text to the getInputProps function. We also have an onChange eventlistener on the input field
<input {...getInputProps({
placeholder: "Search movies",
onChange: this.inputOnChange
})} />
// if the input element is open, render the div else render nothing
{isOpen ? (
<div className="downshift-dropdown">
{
// filter the movies in the state
this.state.movies
.filter(item => !inputValue || item.title.toLowerCase().includes(inputValue.toLowerCase()))
.slice(0, 10) // return just the first ten. Helps improve performance
// map the filtered movies and display their title
.map((item, index) => (
<div
className="dropdown-item"
{...getItemProps({ key: index, index, item })}
style={{
backgroundColor: highlightedIndex === index ? 'lightgray' : 'white',
fontWeight: selectedItem === item ? 'bold' : 'normal',
}}>
{item.title}
</div>
))
}
</div>
) : null}
</div>
)}
</Downshift>
)
}
}
上記のコードには、<Downshift/>
コンポーネントをレンダリングし、それに小道具を渡すクラスコンポーネントがあります。 <Downshift/>
コンポーネントの入力フィールドには、フィールドへの新しい入力をリッスンするonChange
イベントリスナーがあります。
入力がある場合は、fetchMovies
メソッドを呼び出します。このメソッドは、入力フィールドの値を取得し、Axiosを使用してmoviesAPIにAJAXリクエストを送信します。 前の例で行ったように、要求応答をコンポーネントの状態に設定し、それらをフィルター処理して一致するアイテムを返します。
前の例で行ったように、親App
コンポーネントのコンポーネントをインポートしてレンダリングします。 ブラウザにアクセスして、お気に入りの映画を検索してみてください。
ステップ4—ダウンシフトを使用したドロップダウンの作成
ダウンシフトのもう1つの使用例は、ドロップダウンへの電力供給です。 ドロップダウンのAPIは、単純なドロップダウンコンポーネントの構築に役立ちます。 src
フォルダにDownshiftThree.js
ファイルを作成し、これを実現する方法を見てみましょう。
DownshiftThree.js
ファイルに、次のコードを追加します。
import React, { Component } from 'react'
import Downshift from 'downshift';
export default class DownshiftThree extends Component {
constructor(props) {
super(props)
this.books = [
{ name: 'Harry Potter' },
{ name: 'Net Moves' },
{ name: 'Half of a yellow sun' },
{ name: 'The Da Vinci Code' },
{ name: 'Born a crime' },
];
this.state = {
// currently selected dropdown item
selectedBook: ''
}
this.onChange = this.onChange.bind(this)
}
onChange(selectedBook) {
this.setState({ selectedBook: selectedBook.name })
}
render() {
return (
<Downshift onChange={this.onChange} selectedItem={this.state.selectedBook} itemToString={books => (books ? books.name : '')}>
// pass the downshift props into a callback
{({ isOpen, getToggleButtonProps, getItemProps, highlightedIndex, selectedItem: dsSelectedItem, getLabelProps }) => (
<div>
// add a label tag and pass our label text to the getLabelProps function
<label style={{ marginTop: '1rem', display: 'block' }} {...getLabelProps()}>Select your favourite book</label> <br />
// add a button for our dropdown and pass the selected book as its content if there's a selected item
<button className="dropdown-button" {...getToggleButtonProps()}>
{this.state.selectedBook !== '' ? this.state.selectedBook : 'Select a book ...'}
</button>
<div style={{ position: 'relative' }}>
// if the input element is open, render the div else render nothing
{isOpen ? (
<div className="downshift-dropdown">
{
// map through all the books and render them
this.books.map((item, index) => (
<div
className="dropdown-item"
{...getItemProps({ key: index, index, item })}
style={{
backgroundColor: highlightedIndex === index ? 'lightgray' : 'white',
fontWeight: dsSelectedItem === item ? 'bold' : 'normal',
}}>
{item.name}
</div>
))
}
</div>
) : null}
</div>
</div>
)}
</Downshift>
)
}
}
上記のコードには、DownshiftThree
クラスコンポーネントがあり、<Downshift/>
コンポーネントをレンダリングします。 渡されるコールバックには、getToggleButtonProps
関数を渡すボタンがあります。 ボタンには、コンポーネントの状態のselectedBook
が設定されているかどうかに基づいて、ボタンのコンテンツを設定する三項演算子が含まれています。
次に、isOpen
プロップを呼び出して、ドロップダウンが開いているかどうかを確認します。 開いている場合は、すべての本をマップして、ドロップダウンに表示します。
<Downshift/>
コンポーネントに渡されるonChange
メソッドでは、アイテムが選択されるたびに状態が設定され、ボタンのコンテンツが更新されます。 コンポーネントをインポートして親App
コンポーネントにレンダリングし、ブラウザーをリロードして、この時点でアプリを表示します。
ステップ5—ダウンシフトを使用したフォームの作成
この例では、フォームの入力フィールドとしてダウンシフト入力コンポーネントを使用し、フォームデータの送信を試みます。 src
ディレクトリに、DownshiftInputField.js
とDownshiftFour.js
の2つのファイルを作成しましょう。
DownshiftInputField.js
では、Downshiftを使用して入力コンポーネントを作成し、それを使用してDownshiftFour.js
ファイルのいくつかの入力フィールドをレンダリングします。 DownshiftInputField.js
ファイルに機能コンポーネントを作成しましょう。
import React from 'react'
import Downshift from 'downshift';
export default ({ items, onChange, label, placeholder, name }) => {
return (
<Downshift onChange={onChange} itemToString={items => (items ? items.name : '')}>
{({ getInputProps, getItemProps, isOpen, inputValue, highlightedIndex, selectedItem, getLabelProps }) => (
<div>
// add a label tag and pass our label text to the getLabelProps function
<label style={{ marginTop: '1rem', display: 'block' }} {...getLabelProps()}>{label}</label> <br />
// add an input tag and pass our placeholder text to the getInputProps function
<input name={name} {...getInputProps({ placeholder })} />
// if the input element is open, render the div else render nothing
{isOpen ? (
<div className="downshift-dropdown">
{
items
// filter the items and return those that includes the inputValue
.filter(item => !inputValue || item.name.toLowerCase().includes(inputValue.toLowerCase()))
// map through the returned items and render them to the page
.map((item, index) => (
<div
className="dropdown-item"
{...getItemProps({ key: item.name, index, item })}
style={{
backgroundColor: highlightedIndex === index ? 'lightgray' : 'white',
fontWeight: selectedItem === item ? 'bold' : 'normal',
}}>
{item.name}
</div>
))
}
</div>
) : null}
</div>
)}
</Downshift>
)
}
上記のコードでは、機能コンポーネントはアイテムの配列、onChange関数、ラベルとプレースホルダーテキスト、そして最後に名前を受け取ります。 コンポーネントは、コールバック関数で必要なすべての小道具を受け取る<Downshift/>
コンポーネントを返します。 コールバックには、ラベルと入力フィールドがあります。
他の例と同様に、isOpen
プロップを三項演算子に渡します。 入力フィールドが開いている場合は、アイテムの配列をフィルタリングしてinputValue
に一致するアイテムを返し、返されたアイテムをマップしてDOMにレンダリングします。
入力フィールドコンポーネントの準備ができたので、それをDownshiftFour.js
ファイルにインポートしましょう。
import React, { Component } from 'react'
import DownshiftInputField from './DownshiftInputField';
export default class DownshiftFour extends Component {
constructor(props) {
super(props)
this.state = {
books: [
{ name: 'Harry Potter' },
{ name: 'Net Moves' },
{ name: 'Half of a yellow sun' },
{ name: 'The Da Vinci Code' },
{ name: 'Born a crime' },
],
movies: [
{ name: 'Harry Potter' },
{ name: '12 Strong' },
{ name: 'Half of a yellow sun' },
{ name: 'Gringo' },
{ name: 'Black Panther' },
],
book: '',
movie: ''
}
this.onSubmit = this.onSubmit.bind(this);
this.onChange = this.onChange.bind(this);
}
onSubmit(event) {
event.preventDefault();
alert(`
Favourite book: ${this.state.book}
Favourite movie: ${this.state.movie}
has been submitted
`)
}
onChange(selectedBook, stateAndHelpers) {
const element = document.querySelector(`#${stateAndHelpers.id}-input`)
this.setState({ [element.name]: selectedBook.name })
}
render() {
return (
<form onSubmit={this.onSubmit}>
<DownshiftInputField
items={this.state.books}
onChange={this.onChange}
label="Select your favourite book"
name="book"
placeholder="Search your favourite book" />
<DownshiftInputField
items={this.state.movies}
onChange={this.onChange}
label="Select your favourite movie"
name="movie"
placeholder="Search your favourite movie" />
<input type="submit" value="Submit" className="dropdown-button" />
</form>
)
}
}
DownshiftFour.js
ファイルで、入力フィールドコンポーネントをインポートし、クラスコンポーネントを作成しました。 コンポーネントの状態では、本と映画の配列があり、入力フィールドコンポーネントをフォームで2回レンダリングします。1つは本の入力フィールド、もう1つは映画の入力フィールドです。
DownshiftのonChange
関数は、stateAndHelpers
と呼ばれる2番目のパラメーターを取り込んで、Downshiftの現在の状態に関する情報を提供します。
onChange
メソッドでは、stateAndHelpers
引数からid
を取得し、DOMにクエリを実行することで、ユーザーが現在操作している入力フィールドを見つけます。 要素を取得したら、それをコンポーネント状態に設定します。
ユーザーが送信ボタンを押すと、onSubmit
メソッドは、選択されたbook
とmovie
を状態から取得し、必要な処理を実行します。
親のApp
コンポーネントに<DownshiftFour/>
コンポーネントをインポートしてレンダリングし、ブラウザで回転させましょう。
ステップ6—Popper.jsでダウンシフトを使用する
この記事の最後の例では、Popper.jsを使用してダウンシフトポップアップの方向を変更します。 Popperは、ポジショニングツールチップまたはポップアップをより管理しやすいタスクにするライブラリです。
アプリケーションのセットアップ時にreact-popper
パッケージを既にインストールしているので、src
フォルダーにDownshiftFive.js
ファイルを作成しましょう。
次のコードを新しいファイルに追加しましょう。
import React, { Component } from 'react'
import Downshift from 'downshift';
import { Popper, Manager, Target } from 'react-popper';
export default class DownshiftFive extends Component {
// define some default props
static defaultProps = {
positions: [
'top',
'top-start',
'top-end',
'bottom-start',
'bottom',
'bottom-end',
]
}
render() {
return (
<div className="popper-div">
// wrap the whole component in Popper's <Manager/> component
<Manager>
<Downshift render={({ inputValue, getInputProps, getItemProps, isOpen, selectedItem, highlightedIndex }) => (
<div>
// wrap our input element in Popper's <Target/> component
<Target>
<input {...getInputProps({ placeholder: 'Enter a position' })} />
</Target>
<div className="downshift-dropdown">
{isOpen ? (
// pass the selected item to Popper
<Popper
placement={selectedItem || 'bottom'}
style={{ backgroundColor: 'skyblue' }}
>
{
// filter through all the positions and return the ones that include the inputValue
this.props.positions
.filter(item => !inputValue || item.includes(inputValue.toLowerCase()))
// map through all the returned positions and render them
.map((item, index) => (
<div className="downshift-item popper-item"
{...getItemProps({ item })}
key={item}
style={{
cursor: 'pointer',
backgroundColor: highlightedIndex === index ? '#bed5df' : 'transparent',
fontWeight: selectedItem === item ? 'bold' : 'normal',
}}>
{item}
</div>
))
}
</Popper>
) : null}
</div>
</div>
)}></Downshift>
</Manager>
</div>
)
}
}
上記のコードスニペットでは、デフォルトのposition
プロップを使用してDownshiftFive
クラスコンポーネントを作成します。 これらは、Popperがポップアップをレンダリングするために使用する位置です。 コンポーネントのrenderメソッドで、<Manager/>
コンポーネントにラップされた<Downshift/>
コンポーネントを返します。
return (
<div className="popper-div">
<Manager>
<Downshift render={({ inputValue, getInputProps, getItemProps, isOpen, selectedItem, highlightedIndex }) => (
{/* all of our remaining code goes here */}
)}>
</DownShift>
</Manager>
</div>
)
Manager
コンポーネントは、Popperによって公開されるラッパーであり、ページ上の他のすべてのreact-popper
コンポーネントを囲んで、それらを相互に通信させる必要があります。
よく見ると、render
プロップが<Downshift/>
コンポーネントに渡されていることがわかります。 これは、小道具を<Downshift/>
コンポーネントに渡すもう1つの方法です。 基本的に、コールバックを移動してrender
プロップに渡しました。
render
プロップに渡されるコールバックでは、入力フィールドを<Target/>
コンポーネントでラップします。 これは、これがポップアップがレンダリングされる入力フィールドであることをPopperに通知します。
次に、入力が開いているかどうかを確認し、<Popper/>
コンポーネントをレンダリングして、selectedItem
をplacement
プロップに渡します。 これは、新しい位置が選択されるたびに、ポッパーがポップアップを再配置するのに役立ちます。 最後に、他の例と同様に、すべてのデフォルトの小道具の位置をフィルタリングし、inputValue
を含む位置を返し、それらをマッピングしてページにレンダリングします。
最後に、親のApp
コンポーネントに<DownshiftFive/>
コンポーネントをインポートしてレンダリングし、ブラウザでチェックアウトします。 最終製品が表示されます。
結論
この投稿では、Downshiftで解決される一般的なReactドロップダウンのユースケースを調査するためのアプリケーションを作成しました。