I have a Control flow component with React that
children when the condition is true,null or a fallback if the condition is falseinterface Props {
when: boolean
fallback?: () => JSX.Element
children: Children
}
export const Show = ({
when,
fallback,
children
}: Props) => {
if (!when)
return <>{fallback?.() || null}</>
return <>{children}</>
}
if I do not use this component and use a simple binary operator, Typescript works great:
interface Props {
value: {nested: string} | null
}
const SomeComponent =({value}:Props)=>(
<div>
{value && (
<div>
{value.nested}
value is inferred the type "{nested: string}"
</div>
)}
</div>
If I use the control flow component the type is not inferred and typescript gives an error:
interface Props {
value: {nested: string} | null
}
const SomeComponent =({value}:Props)=>(
<div>
<Show when={!!value}>
<div>
{value?.nested}
typeof value remains "{nested: string} | null",
therefore I need some conditional chaining
</div>
</Show>
</div>
Any idea on how to make type inference work?
You can't do it in this way, because the code inside <Show></Show> is actually executed before Show gets a chance to prevent the div from rendering. Think what happens when JSX is transpiled to javascript:
React.createElement(
Show,
{when: !!value},
React.createElement(
'div',
{},
`${value?.nested} typeof value remains "{nested: string} | null", therefore I need some conditional chaining`
)
)
See the problem? If we rewrite it a bit:
const textContent = `${value?.nested} typeof value remains "{nested: string} | null", therefore I need some conditional chaining`
React.createElement(
Show,
{when: !!value},
React.createElement(
'div',
{},
textContent
)
)
These snippets are exactly the same, and in both of them value?.nested is evaluated before Show even renders. I can't recommend you any good solution here, maybe something like this:
interface Props<T> {
value: T
fallback?: () => JSX.Element
children: (value: T) => ReactNode
}
export const CheckExists = <T extends any>({
value,
fallback,
children
}: Props<T>) => {
if (!value)
return fallback ? fallback() : null
return <>{children(value)}</>
}
<CheckExists value={value}>
{existingValue => (
<div>{existingValue.nested}</div>
)}
</CheckExists>
But this is different interface and may not be suitable for you