This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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> { |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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, |