Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save janjakubnanista/323a1731c8acb9f8d346c3bdcbf47e01 to your computer and use it in GitHub Desktop.
Save janjakubnanista/323a1731c8acb9f8d346c3bdcbf47e01 to your computer and use it in GitHub Desktop.
// Approach 3: The compromise
//
// We will add both labelFromValue and itemComponent props to our Select
export interface SelectProps<T> {
// ... previous props
itemComponent: React.ComponentType<SelectItemProps<T>>;
labelFromValue: (value: T) => React.ReactNode;
}
// We will keep the itemComponent props from before, the only extra thing
// that we add is the children prop.
//
// (React actually adds the children prop automatically, I am only doing this
// to be extra explicit)
export interface SelectItemProps<T> {
children: React.ReactNode;
selected: boolean;
value: T;
onToggle: (value: T) => void;
}
export function Select<T>({ items, value, idFromValue, labelFromValue, itemComponent: ItemComponent, onChange }: SelectProps<T>) {
const selectedId = value === undefined ? undefined : idFromValue(value);
const isSelected = (id: string | number) => id === selectedId;
const handleToggle = (value: T) => onChange?.(isSelected(idFromValue(value)) ? undefined : value);
return <div>
{items.map(item => {
const id = idFromValue(item);
const selected = isSelected(id);
// The item label comes from the labelFromValue prop
const label = labelFromValue(item);
// And the UI and UX comes from the itemComponent
return <ItemComponent key={id} value={item} selected={selected} onToggle={handleToggle}>
{label}
</ItemComponent>;
})}
</div>;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment