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

hooks-state.mddocs/

Hooks and State Management

React-Select provides powerful hooks for advanced component integration and custom state management. These hooks allow you to build custom select implementations while leveraging React-Select's core functionality.

Capabilities

useStateManager Hook

Core state management hook that provides the same functionality as the main Select component but with full control over rendering.

/**
 * State management hook providing full control over select state and behavior
 * @param props - State manager props with default values and event handlers
 * @returns Complete state object and methods for managing select behavior
 */
function useStateManager<
  Option = unknown,
  IsMulti extends boolean = false,
  Group extends GroupBase<Option> = GroupBase<Option>
>(props: StateManagerProps<Option, IsMulti, Group>): StateManagerReturn<Option, IsMulti, Group>;

interface StateManagerProps<Option, IsMulti extends boolean, Group extends GroupBase<Option>> {
  /** Default input value */
  defaultInputValue?: string;
  /** Default menu open state */
  defaultMenuIsOpen?: boolean;
  /** Default selected value(s) */
  defaultValue?: PropsValue<Option>;
  /** Input value for controlled input */
  inputValue?: string;
  /** Menu open state for controlled menu */
  menuIsOpen?: boolean;
  /** Selected value(s) for controlled component */
  value?: PropsValue<Option>;
  /** Change handler for value updates */
  onChange?: (newValue: OnChangeValue<Option, IsMulti>, actionMeta: ActionMeta<Option>) => void;
  /** Input change handler */
  onInputChange?: (newValue: string, actionMeta: InputActionMeta) => void;
  /** Menu open handler */
  onMenuOpen?: () => void;
  /** Menu close handler */
  onMenuClose?: () => void;
}

interface StateManagerReturn<Option, IsMulti extends boolean, Group extends GroupBase<Option>> {
  /** Current input value */
  inputValue: string;
  /** Current menu open state */
  menuIsOpen: boolean;
  /** Current selected value(s) */
  value: PropsValue<Option>;
  /** Set input value */
  setValue: (newValue: PropsValue<Option>, actionMeta: ActionMeta<Option>) => void;
  /** Set input text */
  setInputValue: (newValue: string, actionMeta: InputActionMeta) => void;
  /** Set menu open state */
  setMenuIsOpen: (newValue: boolean, actionMeta: ActionMeta<Option>) => void;
}

Usage Examples:

import { useStateManager } from "react-select";

// Custom select with external state control
const CustomSelect = () => {
  const {
    inputValue,
    menuIsOpen,
    value,
    setValue,
    setInputValue,
    setMenuIsOpen
  } = useStateManager({
    defaultValue: null,
    defaultInputValue: "",
    defaultMenuIsOpen: false,
    onChange: (newValue, actionMeta) => {
      console.log("Value changed:", newValue, actionMeta);
      setValue(newValue, actionMeta);
    }
  });

  // Custom rendering logic using state
  return (
    <div>
      <input 
        value={inputValue}
        onChange={(e) => setInputValue(e.target.value, { action: 'input-change' })}
      />
      <button onClick={() => setMenuIsOpen(!menuIsOpen)}>
        {menuIsOpen ? 'Close' : 'Open'} Menu
      </button>
      {/* Custom menu rendering based on state */}
    </div>
  );
};

useAsync Hook

Hook for implementing async option loading functionality in custom select components.

/**
 * Async data loading hook for dynamic option fetching
 * @param props - Async configuration with load function and caching options
 * @returns Async state and methods for loading options
 */
function useAsync<Option = unknown>(props: AsyncProps<Option>): AsyncState<Option>;

interface AsyncProps<Option> {
  /** Default options to show before any input */
  defaultOptions?: OptionsOrGroups<Option, Group> | boolean;
  /** Enable caching of loaded options */
  cacheOptions?: any;
  /** Function to load options based on input value */
  loadOptions?: (
    inputValue: string,
    callback: (options: OptionsOrGroups<Option, Group>) => void
  ) => Promise<OptionsOrGroups<Option, Group>> | void;
}

interface AsyncState<Option> {
  /** Currently loaded options */
  options: OptionsOrGroups<Option, Group>;
  /** Loading state indicator */
  isLoading: boolean;
  /** Error state from failed loads */
  error: Error | null;
  /** Function to trigger option loading */
  loadOptions: (inputValue: string) => void;
  /** Function to clear loaded options */
  clearOptions: () => void;
}

Usage Examples:

import { useAsync } from "react-select";

// Custom async select implementation
const CustomAsyncSelect = () => {
  const {
    options,
    isLoading,
    error,
    loadOptions
  } = useAsync({
    defaultOptions: true,
    cacheOptions: true,
    loadOptions: async (inputValue: string) => {
      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 (
    <div>
      <input 
        onChange={(e) => loadOptions(e.target.value)}
        placeholder="Type to search..."
      />
      {isLoading && <div>Loading...</div>}
      {error && <div>Error: {error.message}</div>}
      <ul>
        {options.map((option) => (
          <li key={option.value}>{option.label}</li>
        ))}
      </ul>
    </div>
  );
};

useCreatable Hook

Hook for implementing creatable option functionality in custom select components.

/**
 * Creatable options hook for dynamic option creation
 * @param props - Creatable configuration with creation handlers
 * @returns Creatable state and methods for managing option creation
 */
function useCreatable<Option = unknown>(props: CreatableProps<Option>): CreatableState<Option>;

interface CreatableProps<Option> {
  /** Allow creating new options */
  allowCreateWhileLoading?: boolean;
  /** Handler for creating new options */
  onCreateOption?: (inputValue: string) => void;
  /** Function to determine if new option can be created */
  isValidNewOption?: (inputValue: string, selectValue: Options<Option>, selectOptions: OptionsOrGroups<Option, Group>) => boolean;
  /** Function to get new option data */
  getNewOptionData?: (inputValue: string, optionLabel: ReactNode) => Option;
  /** Function to format create option label */
  formatCreateLabel?: (inputValue: string) => ReactNode;
  /** Function to determine if option was created by user */
  isOptionDisabled?: (option: Option, selectValue: Options<Option>) => boolean;
}

interface CreatableState<Option> {
  /** Current options including created ones */
  options: OptionsOrGroups<Option, Group>;
  /** Whether a new option can be created for current input */
  canCreateOption: boolean;
  /** Function to create new option */
  createOption: (inputValue: string) => void;
  /** Function to check if option is newly created */
  isNewOption: (option: Option) => boolean;
}

Usage Examples:

import { useCreatable } from "react-select";

// Custom creatable select implementation
const CustomCreatableSelect = () => {
  const [options, setOptions] = useState([
    { value: "chocolate", label: "Chocolate" },
    { value: "strawberry", label: "Strawberry" }
  ]);

  const {
    canCreateOption,
    createOption,
    isNewOption
  } = useCreatable({
    onCreateOption: (inputValue: string) => {
      const newOption = {
        value: inputValue.toLowerCase(),
        label: inputValue,
        __isNew__: true
      };
      setOptions([...options, newOption]);
    },
    isValidNewOption: (inputValue, selectValue, selectOptions) => {
      return inputValue.length > 0 && 
             !selectOptions.some(option => option.label.toLowerCase() === inputValue.toLowerCase());
    },
    formatCreateLabel: (inputValue) => `Create "${inputValue}"`
  });

  return (
    <div>
      {/* Custom implementation using creatable state */}
    </div>
  );
};

State Management Patterns

Controlled vs Uncontrolled Components

React-Select supports both controlled and uncontrolled patterns through the state manager hooks.

Controlled Pattern:

// Fully controlled select with external state
const ControlledSelect = () => {
  const [value, setValue] = useState(null);
  const [inputValue, setInputValue] = useState("");
  const [menuIsOpen, setMenuIsOpen] = useState(false);

  const stateManager = useStateManager({
    value,
    inputValue, 
    menuIsOpen,
    onChange: setValue,
    onInputChange: setInputValue,
    onMenuOpen: () => setMenuIsOpen(true),
    onMenuClose: () => setMenuIsOpen(false)
  });

  // Use stateManager for rendering
};

Uncontrolled Pattern:

// Uncontrolled select with default values
const UncontrolledSelect = () => {
  const stateManager = useStateManager({
    defaultValue: { value: "default", label: "Default Option" },
    defaultInputValue: "",
    defaultMenuIsOpen: false,
    onChange: (value, actionMeta) => {
      // Handle changes without controlling state
      console.log("Selection changed:", value);
    }
  });

  // Use stateManager for rendering
};

Integration with External State Management

React-Select hooks integrate seamlessly with Redux, Zustand, or other state management libraries.

// Redux integration example
const ReduxSelect = () => {
  const dispatch = useDispatch();
  const selectState = useSelector(state => state.select);

  const stateManager = useStateManager({
    value: selectState.value,
    inputValue: selectState.inputValue,
    menuIsOpen: selectState.menuIsOpen,
    onChange: (value, actionMeta) => {
      dispatch(updateSelectValue({ value, actionMeta }));
    },
    onInputChange: (inputValue, actionMeta) => {
      dispatch(updateInputValue({ inputValue, actionMeta }));
    }
  });

  // Render using state manager
};

Types

StateManagerProps

Complete props interface for useStateManager hook.

interface StateManagerProps<Option, IsMulti extends boolean, Group extends GroupBase<Option>> 
  extends Omit<PublicBaseSelectProps<Option, IsMulti, Group>, StateManagedPropKeys> {
  defaultInputValue?: string;
  defaultMenuIsOpen?: boolean;
  defaultValue?: PropsValue<Option>;
}

type StateManagedPropKeys = 
  | 'inputValue' 
  | 'menuIsOpen' 
  | 'onChange' 
  | 'onInputChange' 
  | 'onMenuClose' 
  | 'onMenuOpen' 
  | 'value';

AsyncState and CreatableState

State interfaces returned by async and creatable hooks.

interface AsyncState<Option> {
  options: OptionsOrGroups<Option, Group>;
  isLoading: boolean;
  error: Error | null;
  loadOptions: (inputValue: string) => void;
  clearOptions: () => void;
}

interface CreatableState<Option> {
  options: OptionsOrGroups<Option, Group>;
  canCreateOption: boolean;
  createOption: (inputValue: string) => void;
  isNewOption: (option: Option) => boolean;
}

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