React Select یک کتابخانه متنباز برای پیادهسازی کامپوننت select در React است که توسط Jed Watson توسعه یافته و با هدف رفع محدودیتهای ذاتی المنت HTML <select> طراحی شده است. این کتابخانه مجموعهای از کامپوننتها و APIهای قدرتمند، خوشساخت و آزموده شده را در اختیار توسعهدهندگان قرار میدهد تا امکان ساخت کامپوننتهای Select سفارشی، پیشرفته و انعطافپذیر فراهم شود.
برخی از قابلیتهای کلیدی React Select عبارتاند از:
در این مقاله، مراحل نصب، استفاده و شخصیسازی React Select در پروژههای مدرن React را بهطور کامل بررسی خواهیم کرد. همچنین با گزینههای متنوع پیکربندی این کامپوننت آشنا میشویم تا بتوانیم آن را مطابق با نیازهای دقیق پروژه خود تنظیم کنیم.
چنانچه از نسخههای قدیمیتر استفاده میکنید، توصیه میشود با مراجعه به راهنمای بهروزرسانی، نسخه فعلی را ارتقاء دهید.
المنت <select> در HTML دارای محدودیتهای متعددی است که آن را برای استفاده در اپلیکیشنهای مدرن وب، بهویژه با React، ناکارآمد میسازد. برخی از این محدودیتها عبارتاند از:
کتابخانه React Select این کاستیها را برطرف میسازد و امکانات متنوعی را در اختیار توسعهدهنده قرار میدهد، از جمله:
React Select با هر فریمورک مبتنی بر React سازگار است. برای نصب پکیج react-select، کافی است یکی از دستورات زیر را در ترمینال اجرا کنیم:
npm install react-select # OR yarn add react-select # OR pnpm install react-select
استفاده از React Select بهسادگی افزودن کامپوننت استاندارد Select و ارسال برخی props کلیدی مانند options، onChange و defaultValue است:
import Select from 'react-select';
import { useState } from 'react';
interface Option {
value: string;
label: string;
}
const options: Array<Option> = [
{ value: 'blues', label: 'Blues' },
{ value: 'rock', label: 'Rock' },
{ value: 'jazz', label: 'Jazz' },
{ value: 'orchestra', label: 'Orchestra' }
];
export default function MusicGenreSelect() {
const [selectedOption, setSelectedOption] = useState<Option | null>(null);
return (
<Select<Option>
value={selectedOption}
onChange={(option) => setSelectedOption(option)}
options={options}
isClearable
isSearchable
placeholder="Select a music genre..."
aria-label="Music genre selector"
/>
);
}
در کد بالا، گزینههای انتخابی بهصورت ژانرهای موسیقی تعریف شدهاند و از طریق props به کامپوننت Select ارسال میشوند. مقادیر defaultValue و onChange نیز به مقدار state selectedOption و تابع بهروزرسانی کننده آن setSelectedOption متصل شدهاند. نتیجه نهایی یک کامپوننت Select ساده خواهد بود.
Propsها بخش اصلی عملکرد و همچنین شخصیسازی React Select هستند. علاوهبر propsهایی که در مثال بالا استفاده کردیم، برخی از props رایج دیگر که میتوانیم به کامپوننت Select ارسال کنیم عبارتاند از:
placeholder: Defines the text displayed in the text input
className: Sets a className attribute on the outer or root component
classNamePrefix: If provided, all inner components will be given a prefixed className attribute
autoFocus: Focuses the control when it is mounted
isMulti: Supports multiple selected options
noOptionsMessage: Text to display when there are no options found
menuIsOpen: Opens the dropdown menu by default
isLoading: Useful for async operations. For example, to indicate a loading state during a search
<Select
{...props}
placeholder="Select music genre"
className="adebiyi"
classNamePrefix="logrocket"
autoFocus
isMulti
noOptionsMessage={({ inputValue }) => `No result found for "${inputValue}"`}
/>
React Select این قابلیت را دارد که به کاربران امکان انتخاب چند گزینه را در یک کامپوننت بدهد. برای فعالسازی این ویژگی، کافی است prop مربوط به isMulti را روی کامپوننت Select فعال کنیم:
import Select, { MultiValue } from "react-select";
import { useState } from "react";
const options = [
{ value: "blues", label: "Blues" },
{ value: "rock", label: "Rock" },
{ value: "jazz", label: "Jazz" },
{ value: "orchestra", label: "Orchestra" },
];
export default function App() {
// We now have multiple options. Basically, an array of options.
const [selectedOptions, setSelectedOptions] = useState<MultiValue<{
value: string;
label: string;
}> | null>(null);
return (
<div>
<Select
defaultValue={selectedOptions}
onChange={setSelectedOptions}
options={options}
isMulti
/>
</div>
);
}
ما همچنین میتوانیم استایلهای سفارشی برای انتخاب چندگانه تنظیم کنیم. به این صورت که:
import Select from 'react-select';
import { useState } from 'react';
interface Tag {
value: string;
label: string;
color: string;
}
const customStyles = {
control: (base: any, state: any) => ({
...base,
borderColor: state.isFocused ? '#2684FF' : '#ced4da',
boxShadow: state.isFocused ? '0 0 0 1px #2684FF' : 'none',
'&:hover': {
borderColor: state.isFocused ? '#2684FF' : '#a1a7ae'
}
}),
multiValue: (base: any, { data }: any) => ({
...base,
backgroundColor: data.color,
color: '#fff'
}),
multiValueLabel: (base: any) => ({
...base,
color: 'inherit'
})
};
function TagSelector() {
const [selectedTags, setSelectedTags] = useState<Tag[]>([]);
const options: Tag[] = [
{ value: 'react', label: 'React', color: '#61dafb' },
{ value: 'typescript', label: 'TypeScript', color: '#3178c6' },
{ value: 'javascript', label: 'JavaScript', color: '#f7df1e' }
];
return (
<Select<Tag, true>
isMulti
options={options}
value={selectedTags}
onChange={(newValue) => setSelectedTags(newValue as Tag[])}
styles={customStyles}
placeholder="Select tags..."
closeMenuOnSelect={false}
/>
);
}
Prop مربوط به options در React Select میتواند هم استاتیک و از پیش تعریفشده باشد، مانند مثالهای قبلی، و هم بهصورت داینامیک و asynchronous از طریق API یا کوئری پایگاه داده دریافت شود. برای پیادهسازی این سناریو، کتابخانه React Select کامپوننت Async را از مسیر eact-select/async ارائه میدهد:
import AsyncSelect from 'react-select/async';
import { useState } from 'react';
interface User {
value: string;
label: string;
}
function UserSelect() {
const [selectedUser, setSelectedUser] = useState<User | null>(null);
const loadOptions = async (inputValue: string) => {
try {
const response = await fetch(
`https://api.example.com/users?search=${inputValue}`
);
const data = await response.json();
return data.map((user: any) => ({
value: user.id,
label: user.name
}));
} catch (error) {
console.error('Error loading options:', error);
return [];
}
};
return (
<AsyncSelect<User>
value={selectedUser}
loadOptions={loadOptions}
onChange={setSelectedUser}
isSearchable
placeholder="Search users..."
loadingMessage={() => "Searching..."}
noOptionsMessage={({ inputValue }) =>
inputValue ? `No users found for "${inputValue}"` : "Start typing to search..."
}
/>
);
}
کامپوننت Async قابلیتهای asynchronous مانند وضعیت لودینگ را به کامپوننت Select اضافه میکند.
prop loadOptions یک تابع async یا یک Promise است که متن جستجو (مقدار ورودی) را دریافت میکند و همچنین یک callback در اختیار دارد که بهصورت خودکار زمانی فراخوانی میشود که مقدار ورودی تغییر کند.
کامپوننت Async همچنین دارای props مفیدی مانند موارد زیر است:
cacheOptions: Caching fetched options defaultOptions: Set default options before the remote options are loaded
یکی دیگر از کامپوننتهایی که ممکن است مفید باشد، کامپوننت Fixed Options است که امکان داشتن گزینههای ثابت را فراهم میکند.
در برخی کاربردها ممکن است بخواهیم برخی گزینهها در لیست چندگزینهای بهصورت دائمی انتخابشده باقی بمانند. این گزینهها که اصطلاحاً “Fixed” نامیده میشوند، نباید توسط کاربر قابل حذف باشند.
در این حالت، میتوانیم با بهرهگیری از قابلیتهای سفارشیسازی React Select، گزینههای ثابت را با ظاهری متفاوت نمایش دهیم و با استفاده از منطق موجود در handler مربوط به onChange از حذف آنها جلوگیری نماییم. در مستندات رسمی نیز چنین پیادهسازیای ارائه شده است:
import React, { useState } from 'react';
import Select, { ActionMeta, OnChangeValue, StylesConfig } from 'react-select';
import { ColourOption, colourOptions } from '../data';
// Custom styles to visually differentiate fixed options
const customStyles: StylesConfig<ColourOption, true> = {
multiValue: (base, state) =>
state.data.isFixed ? { ...base, backgroundColor: 'gray' } : base,
multiValueLabel: (base, state) =>
state.data.isFixed
? { ...base, fontWeight: 'bold', color: 'white', paddingRight: 6 }
: base,
multiValueRemove: (base, state) =>
state.data.isFixed ? { ...base, display: 'none' } : base,
};
// Helper function to always position fixed options before non-fixed ones
const orderOptions = (values: readonly ColourOption[]): readonly ColourOption[] => {
return values.filter(v => v.isFixed).concat(values.filter(v => !v.isFixed));
};
export default function FixedOptionsExample() {
// Initialize with a set of fixed and non-fixed options
const [selectedOptions, setSelectedOptions] = useState<readonly ColourOption[]>(
orderOptions([colourOptions[0], colourOptions[1], colourOptions[3]])
);
// Custom change handler to prevent removal of fixed options
const handleChange = (
newValue: OnChangeValue<ColourOption, true>,
actionMeta: ActionMeta<ColourOption>
) => {
switch (actionMeta.action) {
case 'remove-value':
case 'pop-value':
// Prevent removal if the option is fixed
if (actionMeta.removedValue.isFixed) {
return;
}
break;
case 'clear':
// When clearing the selection, preserve only fixed options
newValue = colourOptions.filter(v => v.isFixed);
break;
}
// Reorder options to always show fixed ones first
setSelectedOptions(orderOptions(newValue));
};
return (
<Select
value={selectedOptions}
isMulti
styles={customStyles}
isClearable={selectedOptions.some(v => !v.isFixed)}
name="colors"
className="basic-multi-select"
classNamePrefix="select"
onChange={handleChange}
options={colourOptions}
/>
);
}
در حالت عادی، اگر پس از جستجو گزینهای یافت نشود، فرآیند انتخاب متوقف میشود. اما میتوانیم این قابلیت را به کاربران بدهیم که در صورت نیاز، گزینهای جدید ایجاد کنند. برای این منظور، React Select دو کامپوننت مجزا ارائه میدهد:
Creatable برای گزینههای استاتیکAsyncCreatable برای گزینههای داینامیک و asynchronousاستفاده از Creatable دقیقاً مشابه با Select است:
import Creatable from "react-select/creatable";
import { useState } from "react";
const musicGenres = [
{ value: "blues", label: "Blues" },
{ value: "rock", label: "Rock" },
{ value: "jazz", label: "Jazz" },
{ value: "orchestra", label: "Orchestra" },
];
export default function App() {
const [selectedOption, setSelectedOption] = useState(null);
return (
<>
<div style={{ marginBlockEnd: "1rem", display: "flex" }}>
<span>Selected option:</span>
<pre> {JSON.stringify(selectedOption)} </pre>
</div>
<Creatable options={musicGenres} onChange={setSelectedOption} isMulti />
</>
);
}
و استفاده از AsyncCreatable نیز مشابه Async خواهد بود:
import AsyncCreatable from "react-select/async-creatable";
import { useState } from "react";
const musicGenres = [
{ value: "blues", label: "Blues" },
{ value: "rock", label: "Rock" },
{ value: "jazz", label: "Jazz" },
{ value: "orchestra", label: "Orchestra" },
];
function filterMusicGenre(inputValue) {
return musicGenres.filter((musicGenre) => {
const regex = new RegExp(inputValue, "gi");
return musicGenre.label.match(regex);
});
}
export default function App() {
const [selectedOption, setSelectedOption] = useState(null);
return (
<>
<AsyncCreatable
loadOptions={(inputValue, callback) =>
setTimeout(() => callback(filterMusicGenre(inputValue)), 1000)
}
onChange={setSelectedOption}
isMulti
isClearable
/>
</>
);
}
ادغام React Select با کتابخانه React Hook Form روند مدیریت state فرم و اعتبارسنجی را ساده میکند. مثال زیر نحوه استفاده از کامپوننت Controller از React Hook Form برای اتصال یک کامپوننت Select را به فرم نشان میدهد:
import React from 'react';
import { useForm, Controller } from 'react-hook-form';
import Select from 'react-select';
interface Option {
value: string;
label: string;
}
interface FormData {
category: Option | null;
}
const options: Option[] = [
{ value: 'news', label: 'News' },
{ value: 'sports', label: 'Sports' },
{ value: 'entertainment', label: 'Entertainment' }
];
function FormSelect() {
const { control, handleSubmit } = useForm<FormData>();
const onSubmit = (data: FormData) => {
console.log(data.category);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Controller
name="category"
control={control}
rules={{ required: 'Please select a category' }}
render={({ field, fieldState: { error } }) => (
<div>
<Select
{...field}
options={options}
isClearable
placeholder="Select a category..."
/>
{error && <span className="error">{error.message}</span>}
</div>
)}
/>
<button type="submit">Submit</button>
</form>
);
}
در پروژههایی با مجموعه دادهی بزرگ یا بهروزرسانیهای مکرر، بهینهسازی عملکرد React Select بسیار مهم است. در این مثال، نحوه استفاده از useMemo و useCallback برای اطمینان از اجرای کارآمد عملیاتهای سنگین و فیلترهای سفارشی را مشاهده میکنیم:
import React, { useMemo, useCallback } from 'react';
import Select from 'react-select';
function generateLargeOptionsList() {
// Example: generate a list of options dynamically
return Array.from({ length: 1000 }, (_, i) => ({
value: `option-${i}`,
label: `Option ${i}`
}));
}
function CustomOption(props: any) {
// Custom option component logic
return <div {...props.innerProps}>{props.data.label}</div>;
}
function CustomMultiValue(props: any) {
// Custom multi-value component logic
return <div {...props.innerProps}>{props.data.label}</div>;
}
function OptimizedSelect() {
const options = useMemo(() => generateLargeOptionsList(), []);
const filterOptions = useCallback((inputValue: string) => {
return options.filter(option =>
option.label.toLowerCase().includes(inputValue.toLowerCase())
);
}, [options]);
const customComponents = useMemo(() => ({
Option: CustomOption,
MultiValue: CustomMultiValue
}), []);
return (
<Select
options={options}
filterOption={filterOptions}
components={customComponents}
isSearchable
isClearable
/>
);
}
React Select ایونتهای متعددی را در اختیار ما قرار میدهد تا بتوانیم رفتار کامپوننتهای Select، Async و غیره را مدیریت کنیم. پیشتر با onChange و autoFocus آشنا شدیم. سایر eventهای مهم عبارتاند از:
onBlur onMenuOpen onMenuClose onInputChange onMenuScrollToBottom onMenuScrollToTop
eventهایی که توسط React Select ارائه میشوند، نامگذاری واضح و قابل درکی دارند. برای نمونه، میتوانیم از onBlur برای انجام اعتبارسنجی در هنگام از دست رفتن فوکوس کامپوننت استفاده کنیم.
همچنین در لیستهایی با گزینههای متعدد، میتوانیم از eventهای onMenuScrollToBottom و onMenuScrollToTop برای تشخیص رسیدن به انتهای یا ابتدای منوی انتخاب بهره ببریم. این eventها بهراحتی قابل استفاده در توابع callback هستند و تجربه کاربری بهتری را فراهم میکنند.
در هر یک از این eventها، همانند onBlur، آبجکت event به تابع callback ارسال خواهد شد، مانند مثال زیر:
<Select
{...props}
onMenuOpen={() => console.log("Menu is open")}
onMenuClose={() => console.log("Menu is close")}
onBlur={(e) => console.log(e)}
onMenuScrollToBottom={() =>
console.log("Menu was scrolled to the bottom.")
}
/>
کامپوننت Select از مجموعهای از کامپوننتهای child تشکیل شده که هر کدام دارای استایلهای پایه هستند و میتوانیم این استایلها را بهصورت جداگانه گسترش دهیم یا بازنویسی کنیم. این کامپوننتها شامل control، placeholder، options، noOptionsMessage و سایر بخشها هستند.
سه API برای استایلدهی به این کامپوننتها در اختیار داریم:
stylesclassNamesclassNamePrefixstyles propمیتوانیم یک آبجکت از توابع callback را به prop مربوط به styles ارسال کنیم. هر تابع callback نماینده یکی از کامپوننتهای child است و بهصورت خودکار به استایل پیشفرض آن کامپوننت و state فعلی آن دسترسی دارد.
توجه: لازم نیست که نام آرگومانهای توابع را حتماً “defaultStyles” یا “state” بگذاریم.
import Select from "react-select";
import { useState } from "react";
const options = [
{ value: "blues", label: "Blues" },
{ value: "rock", label: "Rock" },
{ value: "jazz", label: "Jazz" },
{ value: "orchestra", label: "Orchestra" },
];
const customStyles = {
option: (defaultStyles, state) => ({
// You can log the defaultStyles and state for inspection
// You don't need to spread the defaultStyles
...defaultStyles,
color: state.isSelected ? "#212529" : "#fff",
backgroundColor: state.isSelected ? "#a0a0a0" : "#212529",
}),
control: (defaultStyles) => ({
...defaultStyles,
// Notice how these are all CSS properties
backgroundColor: "#212529",
padding: "10px",
border: "none",
boxShadow: "none",
}),
singleValue: (defaultStyles) => ({ ...defaultStyles, color: "#fff" }),
};
export default function App() {
const [selectedOption, setSelectedOption] = useState(null);
return (
<div>
<Select
defaultValue={selectedOption}
onChange={setSelectedOption}
options={options}
styles={customStyles}
/>
</div>
);
}
در مثال بالا، کامپوننتSelect با استفاده از کامپوننتهای child control، option و singleValue بهگونهای استایلدهی شده که ظاهر dark پیدا کند.
classNames propبا استفاده از prop مربوط به classNames میتوانیم به هر کدام از کامپوننتهای child، کلاسهای CSS دلخواه اختصاص دهیم:
<Select
{...props}
classNames={{
control: (state) =>
`border ${state.isFocused ? "border-red-800" : "border-red-400"}`,
option: () => "menu-item",
}}
/>
در کد بالا، حاشیه کامپوننت control با توجه به state isFocused کلاسدهی شده است. این روش معمولترین شیوه استفاده از Tailwind CSS با React Select محسوب میشود.
classNamePrefix propدر حالیکه prop مربوط به className تنها روی المنت root کامپوننت Select اعمال میشود، prop مربوط به classNamePrefix این امکان را فراهم میکند که برای هر کامپوننت child یک فضای نام مشخص شود:
<Select
defaultValue={selectedOption}
onChange={setSelectedOption}
options={options}
className="for-root-component"
classNamePrefix="for-child-components"
/>
کد بالا، در کنار className و classNamePrefix، ساختار DOM زیر را تولید خواهد کرد:
<div class="for-root-component react-select-container">
<div class="for-child-components__control">
<div class="for-child-components__value-container">...</div>
<div class="for-child-components__indicators">...</div>
</div>
<div class="for-child-components__menu">
<div class="for-child-components__menu-list">
<div class="for-child-components__option">...</div>
</div>
</div>
</div>
سپس میتوانیم در فایل .css بهصورت مستقیم هر کلاس را هدف قرار داده و استایلدهی کنیم.
اگر قصد داریم کامپوننت Select را بهطور کامل از نو استایلدهی کنیم، میتوانیم prop مربوط به unstyled را به آن اضافه کرده و کلیه استایلهای پیشفرض را حذف نماییم:
<Select
{...props}
unstyled
/>
پس از حذف استایلها، میتوانیم از یکی از سه API استایلدهی بالا استفاده کرده و استایل دلخواه را اعمال کنیم:
Select props
در صورتی که از styles یا classNames استفاده کنیم، میتوانیم به هر prop سفارشی که به کامپوننت Select ارسال کردهایم، از طریق آرگومان state در توابع callback دسترسی داشته باشیم:
<Select
{...props}
customProps={true} // You can pass a custom prop...
styles={{
control: (defaultStyles, state) => {
// ...then access the props through `selectProps`
// You can use it to style the component
console.log(state.selectProps["customProps"]);
return {
...defaultStyles,
color: state.isSelected ? "#212529" : "#fff",
backgroundColor: state.isSelected ? "#a0a0a0" : "#212529",
};
},
}}
/>
برای استایلدهی مؤثر به Select باید ابتدا مشخص کنیم کدام بخشها را میخواهیم سفارشیسازی کنیم، و سپس یکی از APIهای بالا را بر اساس نیاز خود انتخاب نماییم. اگر نیاز داریم یک کامپوننت را از پایه بازطراحی کنیم، از ابزارهایی مانند cx و کامپوننتهای سفارشی بهره ببریم.
React Select یک کتابخانه قدرتمند برای ساخت کامپوننت select در React است که میتواند بهطور چشمگیری تجربه کاربری اپلیکیشنهای تحت وب را ارتقاء دهد. با دنبالکردن این راهنما و پیادهسازی مثالهای ارائهشده، میتوانیم کامپوننتهایی در دسترس، بهینه و غنی از ویژگی طراحی کنیم که کاملاً با نیازهای پروژههای مدرن سازگار باشند.
برای سناریوهای پیشرفتهتر و دسترسی به مستندات کامل API، میتوانیم به مستندات رسمی React Select مراجعه کنیم. همچنین، اگر در حال ارزیابی کتابخانههای مختلف برای پیادهسازی منوی select در React هستید، مطالعه مقاله «بررسی بهترین کتابخانههای کامپوننت Select در React» میتواند بسیار مفید باشد.
دیدگاهها: