0

I've been struggeling with the typings of this component. It needs to be generic but I an't seem to make it work:

import { clsx } from 'clsx';
import { useCombobox } from 'downshift';
import type { ForwardedRef, ReactNode } from 'react';
import { forwardRef } from 'react';

import { Input } from '../../atoms/Input/Input';

import './Combobox.css';
import { ComboboxProvider } from './ComboboxContext';
import { ComboboxList } from './ComboboxList';
import { ComboboxListItem } from './ComboboxListItem';
import type { ComboboxProps } from './types';

/**
 * ComboBox
 *
 * @description A component with a dropdown, search and filter functionality
 *
 * @example
 * <Combobox>
 *  <Combobox.Prefix>
 *    <Search />
 *  </Combobox.Prefix>
 *  <Combobox.List>
 *    <Combobox.Item>
 *      <Item />
 *    </Combobox.Item>
 *    <Combobox.Item>
 *      <Item />
 *    </Combobox.Item>
 *    <Combobox.Item>
 *      <Item />
 *    </Combobox.Item>
 *    <Combobox.Item>
 *      <Item />
 *    </Combobox.Item>
 *  </Combobox.List>
 *  <Combobox.Suffix>
 *    <Close />
 *  </Combobox.Suffix>
 * </Combobox>
 */

const Combobox = forwardRef(
  <Item,>(
    { placeholder, name, id, defaultValue, className, children, ...props }: ComboboxProps<Item>,
    ref: ForwardedRef<HTMLInputElement>,
  ) => {
    const { getInputProps, ...rest } = useCombobox<Item>(props);

    return (
      <Input
        {...getInputProps({ name, id, placeholder, defaultValue, ref })}
        className={clsx('combobox', className)}
      >
        <ComboboxProvider {...rest}>{children}</ComboboxProvider>
      </Input>
    );
  },
);

const CompoundCombobox = <Item,>() =>
  Object.assign(Combobox<Item>, {
    Prefix: Input.Prefix,
    Suffix: Input.Suffix,
    List: ComboboxList,
    Item: ComboboxListItem,
  });

export { CompoundCombobox as Combobox };

This gives the following error on this line (more specifically on the generic): Object.assign(Combobox<Item>, {

Type 'ForwardRefExoticComponent<Pick<InputProps, "className" | "children" | "placeholder" | "defaultValue" | "name" | "id"> & UseComboboxProps & RefAttributes>' has no signatures for which the type argument list is applicable.

These are the ComboboxProps:

type ComboboxProps<Item> = Pick<
  InputProps,
  'className' | 'children' | 'placeholder' | 'defaultValue' | 'name' | 'id'
> &
  UseComboboxProps<Item>;

I want to be able to use it like this:

<Combobox<ComboboxItemType>
      items={items}
      placeholder="Combobox"
      id="combobox"
      name="combobox"
      itemToString={itemToString}
      onInputValueChange={({ inputValue }) => {
        items.filter((el) => filter(el, inputValue));
      }}
    >
      <Combobox.Prefix>
        <Search />
      </Combobox.Prefix>
      <Combobox.List>
        {items.map((item, index) => (
          <Combobox.Item key={index} index={index} item={item}>
            <div
              style={{
                display: 'flex',
                flexDirection: 'row',
                justifyContent: 'space-between',
                width: '100%',
              }}
            >
              <span>{item.title}</span>
              <span style={{ color: 'var(--neutral-800)' }}>{item.category}</span>
            </div>
          </Combobox.Item>
        ))}
      </Combobox.List>
    </Combobox>
Mout Pessemier
  • 1,665
  • 3
  • 19
  • 33

1 Answers1

0

Two overriding issues:

  1. Making forwardRef generic is infamously annoying and you have to do a dance to get that to work. See this answer.
  2. Your CompoundCombobox was unnecessarily a function. You are just wanting to add those properties onto the component, no need for the function wrapper. You were also referencing a type Combobox<Item> as a value in the first argument of Object.assign. You just want the Combobox there.
type ComboboxType = <Item>(
  props: ComboboxProps<Item> & React.RefAttributes<HTMLInputElement>,
) => React.ReactElement | null;

const Combobox: ComboboxType = forwardRef((props, ref) => {
  const { placeholder, name, id, defaultValue, className, children, ...rest } =
    props;
  const { getInputProps, ...others } = useCombobox<Item>(props);

  return (
    <Input
      {...getInputProps({ name, id, placeholder, defaultValue, ref })}
      className={clsx("combobox", className)}
    >
      <ComboboxProvider {...others}>{children}</ComboboxProvider>
    </Input>
  );
});

const CompoundCombobox = Object.assign(Combobox, {
  Prefix: Input.Prefix,
  Suffix: Input.Suffix,
  List: ComboboxList,
  Item: ComboboxListItem,
});

export { CompoundCombobox as Combobox };
adsy
  • 8,531
  • 2
  • 20
  • 31