Skip to content

Instantly share code, notes, and snippets.

View janjakubnanista's full-sized avatar

Ján Jakub Naništa janjakubnanista

  • LayerZero
  • Vancouver
View GitHub Profile
export function Select<T>({ items, value, idFromValue }: SelectProps<T>) {
// selectedId will be a string/number value that we can use to identify the selected item
const selectedId = value === undefined ? undefined : idFromValue(value);
return <div>
{items.map(item => {
const id = idFromValue(item);
// selected will now be true for values with matching IDs
const selected = id === selectedId;
// Approach 1: let's define a prop that turns a value into a human readable label
//
// In this case Select will be rendering the "item container" and will put
// this label into the container
export interface SelectProps<T> {
// ... previous props
labelFromValue: (value: T) => React.ReactNode;
}
// Approach 2: let's define a whole new component type and let it handle the item rendering completely
// Approach 1: the easy way out
//
// We can just define the return type of our Select
// and make sure it matches the return type of React.FC
function Select<T>(props: SelectProps<T>): React.ReactElement | null {
return null;
}
// Unfortunately we can still pass invalid defaultProps :(
Select.defaultProps = 7;
// Approach 2: diving back in looking for a better way
//
// We can create a type that accepts a type parameter
// and puts a constraint on it, in our case we will demand
// the type parameter to be a React.FC
type AssertComponent<C extends React.FC<any>> = C;
// Then we use it in our Select component
function Select<T>(props: SelectProps<T>) {
return null;
// Approach 3: the light at the end of the tunnel
//
// TypeScript 3.7 introduced "assertion functions" that
// allow us to define an assertion function.
// We might use such function to ensure that anything we pass to it is a React.FC
// while writing no code whatsoever! BINGO!
function assertFC<P>(component: React.FC<P>): asserts component is React.FC<P> {
// We don't need to do anything here because the assertion happens
// on the type level - we need to pass a valid React component
}
// Approach 1: let's define a prop that turns a value into a ReactNode
export interface SelectProps<T> {
// ... Previous props
labelFromValue: (value: T) => React.ReactNode;
}
export function Select<T>({ items, value, idFromValue, labelFromValue, onChange }: SelectProps<T>) {
const selectedId = value === undefined ? undefined : idFromValue(value);
// We will define a little helper just to make things cleaner
// Approach 2: let's define a whole new component type and let it handle the item rendering completely
//
// This way we are free to handle the rendering and selecting/deselecting anyway we want
export interface SelectProps<T> {
// ... previous props
itemComponent: React.ComponentType<SelectItemProps<T>>;
}
// These will be the props of our new item component
export interface SelectItemProps<T> {
// 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
// We will start by defining the props that both the single
// and the multiple versions of our Select have in common
export interface BaseSelectProps<T> {
items: T[];
idFromValue: (value: T) => string | number;
labelFromValue: (value: T) => React.ReactNode;
itemComponent: React.ComponentType<SelectItemProps<T>>;
}
// We then define props specific for the single version
// We can no longer destructure the props - after desctructuring the link
// between our multiple prop and the value/onChange props would vanish
export function Select<T>(props: SelectProps<T>) {
const { idFromValue, itemComponent: ItemComponent, labelFromValue } = props;
// We now "normalize" the props that can take different forms; value and onChange
//
// First we always convert the selected value(s) into an array.
//
// I hope you'll excuse my nested ternary operators and poor choice of a data structure,