npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

multi-selector-dropdown

v1.0.9

Published

A React component for multi-select dropdown with API-driven options, search capability, and add new option functionality

Downloads

17

Readme

Multi-Selector Dropdown

A powerful React component for multi-select dropdown with API-driven options, search capability, and add new option functionality. Built with TypeScript and designed for modern React applications.

✨ Features

  • 🚀 API-Driven Options: Fetch options dynamically from external APIs
  • 🔍 Search Capability: Real-time search and filtering of options
  • ➕ Add New Option: Add new values to the database if not found
  • 📋 Multi-Selection Support: Select multiple options simultaneously
  • ⚡ TypeScript Support: Full TypeScript support with type definitions
  • 🎨 Customizable: Highly configurable with various props and callbacks
  • ♿ Accessible: Built with accessibility in mind
  • 🛡️ Error Handling: Comprehensive error handling for API failures
  • 📱 Responsive: Works seamlessly across different screen sizes
  • ⚡ Performance Optimized: Debounced search and efficient rendering
  • 🎯 Production Ready: Fully tested and ready for production use

📦 Installation

npm install multi-selector-dropdown

🔧 Dependencies

This package requires the following peer dependencies:

npm install react react-dom react-bootstrap-typeahead

CSS Import

You'll also need to import the required CSS in your application:

import 'react-bootstrap-typeahead/css/Typeahead.css';

📸 Snapshot

multi-selector-dropdown-screenshot

🚀 Basic Usage

A simple example showing the basic functionality of the component:

import { useState, useEffect } from 'react';
import { Typeahead } from 'react-bootstrap-typeahead';
import 'bootstrap/dist/css/bootstrap.min.css';
import 'react-bootstrap-typeahead/css/Typeahead.css';
import './App.css'; // Assuming you have some custom styles

interface UserOption {
  id: string | number;
  label: string;
}

function App() {
  const [selectedOptions, setSelectedOptions] = useState<UserOption[]>([]);
  const [options, setOptions] = useState<UserOption[]>([]);

  useEffect(() => {
    const dummyUsers: UserOption[] = [
      { id: '1', label: 'Alice Johnson' },
      { id: '2', label: 'Bob Smith' },
      { id: '3', label: 'Charlie Davis' },
      { id: '4', label: 'Diana Ross' },
      { id: '5', label: 'Ethan Brown' },
    ];
    setOptions(dummyUsers);
  }, []);

  const handleAddNew = (value: string): UserOption => {
    const newUser = { id: `new-${Date.now()}`, label: value };
    setOptions((prev) => [...prev, newUser]);
    return newUser;
  };

  return (
    <div className="min-vh-100 d-flex align-items-center justify-content-center bg-light">
      <div className="container p-4 rounded-4 shadow-lg" style={{ maxWidth: '900px', backgroundColor: '#ffffff' }}>
        <h2 className="text-center text-primary fw-bold mb-4">Multi-Selector Dropdown</h2>
        <p className="text-center text-muted mb-5">
          This example uses hardcoded data instead of fetching from an API.
        </p>

        <div className="row justify-content-center">
          <div className="col-md-10">
            <label className="form-label text-dark fw-semibold">Select Users*</label>
            <Typeahead
              id="user-multi-select"
              labelKey="label"
              multiple
              allowNew
              newSelectionPrefix="Add new user: "
              options={options}
              placeholder="Search and select users..."
              onChange={(selected: any[]) => {
                const cleanOptions: UserOption[] = selected.map((item) =>
                  typeof item === 'string' ? handleAddNew(item) : item
                );
                setSelectedOptions(cleanOptions);
              }}
              selected={selectedOptions}
              className="bg-white rounded-3 shadow-sm border border-secondary"
            />
          </div>
        </div>

        {selectedOptions.length > 0 && (
          <div className="mt-5">
            <h4 className="text-dark text-center fw-semibold mb-4">Selected Users</h4>
            <div className="table-responsive">
              <table className="table table-striped table-hover table-bordered">
                <thead className="table-primary">
                  <tr>
                    <th>ID</th>
                    <th>Name</th>
                  </tr>
                </thead>
                <tbody>
                  {selectedOptions.map((option) => (
                    <tr key={option.id} className="align-middle">
                      <td>{option.id}</td>
                      <td>{option.label}</td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
          </div>
        )}
      </div>
    </div>
  );
}

export default App;

🎯 Advanced Usage

A comprehensive example demonstrating advanced features like multiple dropdowns, different configurations, and complex state management:

import React, { useState } from 'react';
import MultiSelectorDropdown from 'multi-selector-dropdown';
import { Option } from 'multi-selector-dropdown';
import 'react-bootstrap-typeahead/css/Typeahead.css';

const AdvancedExample = () => {
  const [selectedUsers, setSelectedUsers] = useState<Option[]>([]);
  const [selectedProducts, setSelectedProducts] = useState<Option[]>([]);
  const [selectedCategories, setSelectedCategories] = useState<Option[]>([]);

  // Mock API function for adding new users
  const handleAddNewUser = async (value: string): Promise<Option | null> => {
    await new Promise(resolve => setTimeout(resolve, 300));
    return {
      id: `user-${Date.now()}`,
      label: value,
      type: 'user'
    };
  };

  // Mock API function for adding new products
  const handleAddNewProduct = async (value: string): Promise<Option | null> => {
    await new Promise(resolve => setTimeout(resolve, 300));
    return {
      id: `product-${Date.now()}`,
      label: value,
      type: 'product'
    };
  };

  // Mock API function for adding new categories
  const handleAddNewCategory = async (value: string): Promise<Option | null> => {
    await new Promise(resolve => setTimeout(resolve, 300));
    return {
      id: `category-${Date.now()}`,
      label: value,
      type: 'category'
    };
  };

  const handleUserChange = (options: Option[]) => {
    setSelectedUsers(options);
    console.log('Selected users:', options);
  };

  const handleProductChange = (options: Option[]) => {
    setSelectedProducts(options);
    console.log('Selected products:', options);
  };

  const handleCategoryChange = (options: Option[]) => {
    setSelectedCategories(options);
    console.log('Selected categories:', options);
  };

  return (
    <div className="container mt-5">
      <h2>Multi-Selector Dropdown - Advanced Example</h2>
      <p className="text-muted">
        This example demonstrates advanced features including multiple dropdowns, 
        different configurations, and complex state management.
      </p>
      
      <div className="row">
        <div className="col-md-4">
          <h4>Users Dropdown</h4>
          <MultiSelectorDropdown
            apiUrl="https://jsonplaceholder.typicode.com/users"
            placeholder="Search and select users..."
            label="Select Users"
            required={true}
            allowAddNew={true}
            addNewPrefix="Add new user: "
            onAddNew={handleAddNewUser}
            onChange={handleUserChange}
            multiple={true}
            maxSelections={3}
            clearButton={true}
            size="sm"
            error=""
            touched={false}
          />
        </div>

        <div className="col-md-4">
          <h4>Products Dropdown</h4>
          <MultiSelectorDropdown
            apiUrl="https://jsonplaceholder.typicode.com/posts"
            placeholder="Search and select products..."
            label="Select Products"
            required={false}
            allowAddNew={true}
            addNewPrefix="Add new product: "
            onAddNew={handleAddNewProduct}
            onChange={handleProductChange}
            multiple={true}
            maxSelections={5}
            clearButton={true}
            size="md"
            error=""
            touched={false}
          />
        </div>

        <div className="col-md-4">
          <h4>Categories Dropdown</h4>
          <MultiSelectorDropdown
            apiUrl="https://jsonplaceholder.typicode.com/albums"
            placeholder="Search and select categories..."
            label="Select Categories"
            required={false}
            allowAddNew={true}
            addNewPrefix="Add new category: "
            onAddNew={handleAddNewCategory}
            onChange={handleCategoryChange}
            multiple={false}
            clearButton={true}
            size="lg"
            error=""
            touched={false}
          />
        </div>
      </div>

      <div className="row mt-4">
        <div className="col-md-4">
          <h5>Selected Users ({selectedUsers.length})</h5>
          <ul className="list-group">
            {selectedUsers.map((user) => (
              <li key={user.id} className="list-group-item">
                <strong>ID:</strong> {user.id} | <strong>Name:</strong> {user.label}
              </li>
            ))}
          </ul>
        </div>

        <div className="col-md-4">
          <h5>Selected Products ({selectedProducts.length})</h5>
          <ul className="list-group">
            {selectedProducts.map((product) => (
              <li key={product.id} className="list-group-item">
                <strong>ID:</strong> {product.id} | <strong>Name:</strong> {product.label}
              </li>
            ))}
          </ul>
        </div>

        <div className="col-md-4">
          <h5>Selected Categories ({selectedCategories.length})</h5>
          <ul className="list-group">
            {selectedCategories.map((category) => (
              <li key={category.id} className="list-group-item">
                <strong>ID:</strong> {category.id} | <strong>Name:</strong> {category.label}
              </li>
            ))}
          </ul>
        </div>
      </div>

      <div className="mt-4">
        <h4>Summary</h4>
        <div className="alert alert-info">
          <strong>Total Selected Items:</strong> {selectedUsers.length + selectedProducts.length + selectedCategories.length}
          <br />
          <strong>Users:</strong> {selectedUsers.length} | 
          <strong>Products:</strong> {selectedProducts.length} | 
          <strong>Categories:</strong> {selectedCategories.length}
        </div>
      </div>
    </div>
  );
};

export default AdvancedExample;

🔧 Recent Updates & Fixes

Version 1.0.3 - Production Ready

Major Improvements:

  • 🔧 Fixed Dropdown UI Issues: Improved styling and dropdown appearance
  • 🎯 Fixed Options Display: Options now appear correctly when clicking input field
  • ⚡ Enhanced Search Functionality: Real-time search with proper debouncing
  • 🛠️ Improved Add New Feature: Better handling of custom option creation
  • 📱 Better Mobile Support: Enhanced responsive design
  • 🧪 Comprehensive Testing: All tests passing (8/8)
  • 📦 Build Optimization: Clean TypeScript compilation

Technical Fixes:

  • Fixed selected={[]} prop to ensure dropdown shows options
  • Improved filteredOptions logic for proper option filtering
  • Enhanced search term state management
  • Added proper error handling for API calls
  • Optimized component re-rendering
  • Fixed TypeScript type issues

API Reference

Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | apiUrl | string | - | Required. The URL to fetch options from | | apiMethod | 'GET' \| 'POST' | 'GET' | HTTP method for API requests | | apiHeaders | Record<string, string> | {} | Additional headers for API requests | | apiParams | Record<string, any> | {} | Additional parameters for API requests | | placeholder | string | 'Select options...' | Placeholder text for the input | | label | string | - | Label for the dropdown | | required | boolean | false | Whether the field is required | | disabled | boolean | false | Whether the dropdown is disabled | | className | string | '' | Additional CSS classes | | searchEnabled | boolean | true | Enable search functionality | | searchPlaceholder | string | - | Placeholder for search input | | minSearchLength | number | 0 | Minimum characters before triggering search | | allowAddNew | boolean | true | Allow adding new options | | addNewPrefix | string | 'Add new: ' | Prefix for new option suggestions | | onAddNew | (value: string) => Promise<Option \| null> | - | Callback for adding new options | | multiple | boolean | true | Enable multi-selection | | maxSelections | number | - | Maximum number of selections allowed | | onChange | (selectedOptions: Option[]) => void | - | Callback when selection changes | | onSearch | (searchTerm: string) => void | - | Callback when search term changes | | onBlur | () => void | - | Callback when input loses focus | | onFocus | () => void | - | Callback when input gains focus | | value | Option[] | [] | Controlled value | | defaultValue | Option[] | [] | Default value | | clearButton | boolean | true | Show clear button | | size | 'sm' \| 'md' \| 'lg' | 'md' | Size of the dropdown | | error | string | - | Error message to display | | touched | boolean | false | Whether the field has been touched |

Types

Option

interface Option {
  id: string | number;
  label: string;
  [key: string]: any;
}

ApiResponse

interface ApiResponse<T> {
  data: T[];
  total?: number;
  page?: number;
  limit?: number;
  [key: string]: any;
}

API Response Format

The component expects the API to return data in one of these formats:

Array Format

[
  { "id": 1, "label": "Option 1" },
  { "id": 2, "label": "Option 2" }
]

Object Format

{
  "data": [
    { "id": 1, "label": "Option 1" },
    { "id": 2, "label": "Option 2" }
  ],
  "total": 2,
  "page": 1,
  "limit": 10
}

Styling

The component uses Bootstrap classes by default. You can customize the styling by:

  1. Overriding CSS classes
  2. Using the className prop
  3. Customizing the Bootstrap theme

Custom CSS Example

.multi-selector-dropdown .rbt-input-main {
  border-radius: 0.375rem;
}

.multi-selector-dropdown .btn-outline {
  border-radius: 50px;
}

.multi-selector-dropdown .checkbox-pill {
  background-color: #007bff;
  border-color: #007bff;
}

Error Handling

The component includes built-in error handling for:

  • API request failures
  • Network errors
  • Invalid responses
  • Add new option failures

Errors are logged to the console and can be handled through the onAddNew callback.

Accessibility

The component is built with accessibility in mind:

  • Proper ARIA labels
  • Keyboard navigation support
  • Screen reader compatibility
  • Focus management

Browser Support

  • Chrome (latest)
  • Firefox (latest)
  • Safari (latest)
  • Edge (latest)

Testing

The component includes comprehensive tests:

npm test

All tests are passing and cover:

  • Component rendering
  • API integration
  • Search functionality
  • Add new option feature
  • Multi-selection behavior
  • Error handling

Development

Local Testing

# Build the package
npm run build

# Run tests
npm test

# Link for local development
npm link

Publishing

# Build and publish
npm run build
npm publish

License

MIT

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Add tests
  5. Submit a pull request

💡 Contributing

Pull requests and issues are welcome! Please open an issue or PR on GitHub.


👤 Author

Akshay Bhalala
[email protected]


🤝 Contributing

We welcome contributions! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.

📝 License

This project is licensed under the MIT License - see the LICENSE file for details.

🔑 Keywords

  • react
  • dropdown
  • multi-select
  • typeahead
  • search
  • api
  • typescript
  • bootstrap
  • form-control
  • autocomplete
  • select
  • input
  • ui-component
  • react-component
  • javascript
  • frontend
  • web-development
  • user-interface
  • interactive
  • responsive