In ReactJS with TypeScript (both latest stable at the time of writing of this post) I can define a component that conditionally renders its children if and only if a given property is truthy. One possible implementation could be:
// I will be using this type helper in subsequent examples
export type Maybe<T> = T | undefined | null
export function Only<T>(props: {
when: Maybe<T>
children: ReactNode
}) {
const {when, children} = props
return <>{when ? children : null}</>
}
It can then be used in the following manner:
const MyComponent = (props: { value: number }) => (
<div>Value: {props.value}</div>
);
export function MyPage(){
const [value, setValue] = useState<number>()
// value: number | undefined
setTimeout(()=>{
// Simulate a request
setValue(42)
}, 1000)
return (
<div>
<Only when={value}>
<MyComponent value={value!}/>
</Only>
</div>
)
}
It behaves as desired, but the issue is that I have to use the non-null assertion operator ! in order to pass the value to MyComponent. Fortunately, I am able to solve it by passing the narrowed type to children:
export function Only<T>(props: {
when: Maybe<T>
children: ReactNode | ((when: T) => ReactNode)
}) {
const {when, children} = props;
return when ?
<>{children instanceof Function ? children(when) : children}</>
: null
}
I would then use it in the following manner:
<Only when={value}>{value => // Overshadowing with narrowed type
<MyComponent value={value}/>
}</Only>
Once again, this works as expected, albeit introducing a little bit of verbosity.
But what if I need to conditionally render a component based on "truthiness" of more than one property and pass them down to children with narrowed types? One obvious solution is to nest the aforementioned Only components:
const MyComponent = (props: { value1: number, value2: string }) => (
<>
<div>Value1: {props.value1}</div>
<div>Value2: {props.value2}</div>
</>
);
export function MyPage() {
const [value1, setValue1] = useState<number>()
const [value2, setValue2] = useState<string>()
setTimeout(() => {
// Simulate a request
setValue1(42)
setValue2('foo')
}, 1000)
return (
<div>
<Only when={value1}>{value1 =>
<Only when={value2}>{value2 =>
<MyComponent value1={value1} value2={value2}/>
}</Only>
}</Only>
</div>
)
}
Even though it will work with any number of properties, it is easy to see how this approach is not scalable given the sheer amount of boilerplate code we would need to write in order to pass several properties this way. Ideally, we would pass those properties in an array, whose members will be narrowed down in a similar manner. For example, a special case for 2 properties would look like this:
export function Only2<T, S>(props: {
when: [Maybe<T>, Maybe<S>]
children: ReactNode | ((when: [T, S]) => ReactNode)
}) {
const {when, children} = props
return when[0] && when[1] ?
<>{children instanceof Function ? children([when[0], when[1]]) : children}</>
: null
}
It would then be used in the following manner:
<Only2 when={[value1, value2]}>{([value1, value2]) =>
<MyComponent value1={value1} value2={value2}/>
}</Only2>
Unfortunately, I am unable to come up with a solution for a general case, where we would be able to pass and narrow down types of any number of values in a convenient manner as shown in the snippet above. Hence my question is twofold: Is it even possible to achieve the result that I am looking for in TypeScript? And if not, are there any alternative strategies for achieving a generic component that could be used in a convenient manner as was shown in the snippets that used Only component?