CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-react-select

A Select control built with and for ReactJS that provides comprehensive dropdown functionality with support for single/multi-select, async data loading, search filtering, and extensive customization.

Pending
Overview
Eval results
Files

async-data.mddocs/

Async Data Loading

React-Select provides powerful async data loading capabilities through AsyncSelect and AsyncCreatableSelect components, along with the useAsync hook for custom implementations. This enables dynamic option fetching, caching, and loading state management.

Capabilities

AsyncSelect Component

Select component with built-in async option loading, caching, and loading state management.

/**
 * Select component with async option loading capabilities
 * @param props - Standard Props plus AsyncProps for async functionality
 * @returns JSX.Element with async loading capabilities
 */
function AsyncSelect<
  Option = unknown,
  IsMulti extends boolean = false,
  Group extends GroupBase<Option> = GroupBase<Option>
>(props: Props<Option, IsMulti, Group> & AsyncProps<Option>): JSX.Element;

interface AsyncProps<Option> {
  /** Default options to show before any input, or true to load options immediately */
  defaultOptions?: OptionsOrGroups<Option, Group> | boolean;
  /** Enable caching of loaded options to avoid repeated API calls */
  cacheOptions?: any;
  /** 
   * Function to load options asynchronously based on input value
   * Can return Promise or use callback pattern
   */
  loadOptions?: (
    inputValue: string,
    callback: (options: OptionsOrGroups<Option, Group>) => void
  ) => Promise<OptionsOrGroups<Option, Group>> | void;
}

Usage Examples:

import AsyncSelect from "react-select/async";

// Promise-based async loading
const PromiseAsyncSelect = () => {
  const loadOptions = async (inputValue: string): Promise<Option[]> => {
    const response = await fetch(`/api/search?q=${inputValue}`);
    const data = await response.json();
    return data.map((item: any) => ({
      value: item.id,
      label: item.name,
    }));
  };

  return (
    <AsyncSelect
      cacheOptions
      defaultOptions
      loadOptions={loadOptions}
      placeholder="Type to search..."
      noOptionsMessage={({ inputValue }) =>
        inputValue ? `No results for "${inputValue}"` : "Type to search"
      }
    />
  );
};

// Callback-based async loading
const CallbackAsyncSelect = () => {
  const loadOptions = (inputValue: string, callback: Function) => {
    if (!inputValue) {
      callback([]);
      return;
    }

    setTimeout(() => {
      const filteredOptions = allOptions.filter((option) =>
        option.label.toLowerCase().includes(inputValue.toLowerCase())
      );
      callback(filteredOptions);
    }, 1000);
  };

  return (
    <AsyncSelect
      loadOptions={loadOptions}
      placeholder="Type to search..."
      cacheOptions
    />
  );
};

// With error handling
const ErrorHandlingAsync = () => {
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const loadOptions = async (inputValue: string) => {
    if (!inputValue) return [];
    
    setIsLoading(true);
    setError(null);
    
    try {
      const response = await api.searchUsers(inputValue);
      return response.data;
    } catch (err) {
      setError('Failed to load options');
      return [];
    } finally {
      setIsLoading(false);
    }
  };

  return (
    <div>
      <AsyncSelect
        loadOptions={loadOptions}
        isLoading={isLoading}
        placeholder={error ? error : "Search users..."}
      />
    </div>
  );
};

Default Options Configuration

Control initial option loading behavior and caching strategies.

/**
 * defaultOptions configuration for AsyncSelect
 */
type DefaultOptions<Option, Group extends GroupBase<Option>> = 
  | OptionsOrGroups<Option, Group>  // Specific default options
  | boolean;                        // true = load immediately, false = no defaults

/**
 * cacheOptions configuration for result caching
 */
type CacheOptions = any;  // Enable/disable caching of loadOptions results

Usage Examples:

// Preload specific default options
const PreloadedAsync = () => (
  <AsyncSelect
    defaultOptions={[
      { value: 'popular1', label: 'Popular Option 1' },
      { value: 'popular2', label: 'Popular Option 2' },
    ]}
    loadOptions={loadOptions}
  />
);

// Auto-load on mount
const AutoLoadAsync = () => (
  <AsyncSelect
    defaultOptions={true}  // Calls loadOptions('') on mount
    loadOptions={loadOptions}
    cacheOptions={true}    // Cache results
  />
);

// No defaults, load only on input
const OnDemandAsync = () => (
  <AsyncSelect
    defaultOptions={false}  // No initial options
    loadOptions={loadOptions}
    placeholder="Start typing to search..."
  />
);

Load Options Function Patterns

Different patterns for implementing the loadOptions function.

/**
 * Load options function - Promise pattern
 * @param inputValue - Current input value for filtering
 * @returns Promise resolving to options array
 */
type LoadOptionsPromise<Option, Group extends GroupBase<Option>> = (
  inputValue: string
) => Promise<OptionsOrGroups<Option, Group>>;

/**
 * Load options function - Callback pattern  
 * @param inputValue - Current input value for filtering
 * @param callback - Function to call with loaded options
 */
type LoadOptionsCallback<Option, Group extends GroupBase<Option>> = (
  inputValue: string,
  callback: (options: OptionsOrGroups<Option, Group>) => void
) => void;

Usage Examples:

// REST API integration
const restApiLoader = async (inputValue: string) => {
  const params = new URLSearchParams({
    search: inputValue,
    limit: '20',
  });
  
  const response = await fetch(`/api/options?${params}`);
  if (!response.ok) throw new Error('Failed to fetch');
  
  const data = await response.json();
  return data.items.map((item: any) => ({
    value: item.id,
    label: item.displayName,
    ...item,
  }));
};

// GraphQL integration
const graphqlLoader = async (inputValue: string) => {
  const query = `
    query SearchOptions($search: String!) {
      searchOptions(search: $search) {
        id
        name
        description
      }
    }
  `;
  
  const response = await graphqlClient.query({
    query,
    variables: { search: inputValue },
  });
  
  return response.data.searchOptions.map((item: any) => ({
    value: item.id,
    label: item.name,
    meta: item.description,
  }));
};

// Debounced loading
const debouncedLoader = useMemo(
  () => debounce(async (inputValue: string) => {
    return await api.search(inputValue);
  }, 300),
  []
);

// Grouped options loading
const groupedLoader = async (inputValue: string) => {
  const [users, teams] = await Promise.all([
    api.searchUsers(inputValue),
    api.searchTeams(inputValue),
  ]);
  
  return [
    {
      label: 'Users',
      options: users.map(user => ({ value: user.id, label: user.name })),
    },
    {
      label: 'Teams', 
      options: teams.map(team => ({ value: team.id, label: team.name })),
    },
  ];
};

useAsync Hook

Hook for implementing custom async behavior with select components.

/**
 * Hook for managing async select state and behavior
 * @param props - Async hook configuration
 * @returns Object with async state and handlers
 */
function useAsync<Option, Group extends GroupBase<Option>>(
  props: UseAsyncProps<Option, Group>
): UseAsyncResult<Option, Group>;

interface UseAsyncProps<Option, Group extends GroupBase<Option>> {
  defaultOptions?: OptionsOrGroups<Option, Group> | boolean;
  cacheOptions?: any;
  loadOptions?: LoadOptionsFunction<Option, Group>;
}

interface UseAsyncResult<Option, Group extends GroupBase<Option>> {
  /** Current loaded options */
  options: OptionsOrGroups<Option, Group>;
  /** Whether async loading is in progress */
  isLoading: boolean;
  /** Current input value being searched */
  inputValue: string;
  /** Handler for input changes that triggers loading */
  onInputChange: (inputValue: string, actionMeta: InputActionMeta) => void;
  /** Handler for menu open events */
  onMenuOpen: () => void;
}

Usage Examples:

import { BaseSelect, useAsync } from "react-select";

// Custom async select with additional logic
const CustomAsyncSelect = (props) => {
  const asyncProps = useAsync({
    defaultOptions: true,
    cacheOptions: true,
    loadOptions: props.loadOptions,
  });
  
  return (
    <BaseSelect
      {...props}
      {...asyncProps}
      filterOption={null}  // Disable client-side filtering
    />
  );
};

// Async with custom loading states
const EnhancedAsyncSelect = () => {
  const [error, setError] = useState(null);
  
  const loadOptions = async (inputValue: string) => {
    try {
      setError(null);
      return await api.search(inputValue);
    } catch (err) {
      setError(err.message);
      return [];
    }
  };
  
  const asyncProps = useAsync({
    loadOptions,
    cacheOptions: true,
  });
  
  return (
    <div>
      {error && <div className="error">{error}</div>}
      <BaseSelect
        {...asyncProps}
        loadingMessage={() => "Searching..."}
        noOptionsMessage={({ inputValue }) => 
          error ? "Error loading options" : `No results for "${inputValue}"`
        }
      />
    </div>
  );
};

AsyncCreatableSelect Integration

Combining async loading with option creation capabilities.

/**
 * Select combining async loading and option creation
 * @param props - Combined async and creatable props
 * @returns JSX.Element with both capabilities
 */
function AsyncCreatableSelect<
  Option = unknown,
  IsMulti extends boolean = false,
  Group extends GroupBase<Option> = GroupBase<Option>
>(
  props: Props<Option, IsMulti, Group> & AsyncProps<Option> & CreatableProps<Option>
): JSX.Element;

interface AsyncCreatableProps<Option> extends AsyncProps<Option>, CreatableProps<Option> {
  /** Allow creating options while async loading is in progress */
  allowCreateWhileLoading?: boolean;
}

Usage Examples:

import AsyncCreatableSelect from "react-select/async-creatable";

// Tags with async loading and creation
const TagsAsyncCreatable = () => {
  const loadOptions = async (inputValue: string) => {
    const response = await api.searchTags(inputValue);
    return response.map(tag => ({ value: tag.id, label: tag.name }));
  };
  
  const handleCreate = async (inputValue: string) => {
    const newTag = await api.createTag({ name: inputValue });
    return { value: newTag.id, label: newTag.name };
  };
  
  return (
    <AsyncCreatableSelect
      isMulti
      cacheOptions
      defaultOptions={false}
      loadOptions={loadOptions}
      onCreateOption={handleCreate}
      allowCreateWhileLoading={true}
      formatCreateLabel={(inputValue) => `Create tag "${inputValue}"`}
      noOptionsMessage={({ inputValue }) =>
        inputValue ? `No existing tags found. Type to create "${inputValue}"` : "Start typing to search tags"
      }
    />
  );
};

Performance Optimization

Caching Strategies

// Memory-efficient caching with size limits
const createCachedLoader = (maxCacheSize = 100) => {
  const cache = new Map();
  
  return async (inputValue: string) => {
    if (cache.has(inputValue)) {
      return cache.get(inputValue);
    }
    
    const result = await api.search(inputValue);
    
    // Implement LRU cache
    if (cache.size >= maxCacheSize) {
      const firstKey = cache.keys().next().value;
      cache.delete(firstKey);
    }
    
    cache.set(inputValue, result);
    return result;
  };
};

// Time-based cache expiration
const createTimeCachedLoader = (ttlMs = 300000) => {  // 5 minutes
  const cache = new Map();
  
  return async (inputValue: string) => {
    const cached = cache.get(inputValue);
    if (cached && Date.now() - cached.timestamp < ttlMs) {
      return cached.data;
    }
    
    const result = await api.search(inputValue);
    cache.set(inputValue, {
      data: result,
      timestamp: Date.now(),
    });
    
    return result;
  };
};

Debouncing and Throttling

// Debounced loading to reduce API calls
const useDebouncedAsync = (loadOptions: Function, delay = 300) => {
  const debouncedLoader = useMemo(
    () => debounce(loadOptions, delay),
    [loadOptions, delay]
  );
  
  return debouncedLoader;
};

// Usage
const DebouncedAsyncSelect = () => {
  const baseLoader = async (inputValue: string) => {
    return await api.search(inputValue);
  };
  
  const debouncedLoader = useDebouncedAsync(baseLoader, 500);
  
  return (
    <AsyncSelect
      loadOptions={debouncedLoader}
      cacheOptions
    />
  );
};

Install with Tessl CLI

npx tessl i tessl/npm-react-select

docs

async-data.md

core-components.md

customization.md

hooks-state.md

index.md

types-interfaces.md

tile.json