S
SolidJS7mo ago
aryzing

Typesafe way to render conditionally based on whether array has zero, one or many elements?

Given an array with one or more elements, the idea is to display different UIs. With React, this can be done in a typesafe way by returning different JSX after an if check,
if (array.length === 0) {
// return emtpy sate
}

if (array[0] && array.length === 1) {
const item = array[0] // this has a non-null type
// return jsx
}

return array.map(...)
if (array.length === 0) {
// return emtpy sate
}

if (array[0] && array.length === 1) {
const item = array[0] // this has a non-null type
// return jsx
}

return array.map(...)
How can the same be achieved with Solid? Seems that <Switch /> / <Match /> was a good place to start, although the where's boolean check doesn't perform type narrowing, so the typecheck fails: the array could be empty and accessing the first element could return undefined. When adding <Show /> to the mix, the type safety can be improved,
<Switch>
<Match when={array.length === 0}>...</Match>
<Match when={array.length === 1}>
<Show when={array[0]}>
{(item) => (<>
{/* Here `item` is typed */}
<p>{item().foo}</p>
</>)}
</Show>
</Match>
<Match when={array.length > 1}>...</Match>
</Switch>
<Switch>
<Match when={array.length === 0}>...</Match>
<Match when={array.length === 1}>
<Show when={array[0]}>
{(item) => (<>
{/* Here `item` is typed */}
<p>{item().foo}</p>
</>)}
</Show>
</Match>
<Match when={array.length > 1}>...</Match>
</Switch>
although this seems overly verbose, and I feel uneasy about using <Show> just for its non-null assertion. Are there better options?
2 Replies
thetarnav
thetarnav7mo ago
if the array is static, just go with react version if it's not, this might be better:
<Show
when={array().length > 1}
fallback={(() => {
const array_value = array()

if (array_value.length === 1) {
...
} else {
...
}
})()}
>
...
</Show>
<Show
when={array().length > 1}
fallback={(() => {
const array_value = array()

if (array_value.length === 1) {
...
} else {
...
}
})()}
>
...
</Show>
it should work the same way as array().length > 1 will still be memoized but it may not be worth the "typesafety"
aryzing
aryzing7mo ago
Much appreciated @thetarnav . After trying out your suggestion and a few others, I landed on the following,
<Match when={cond1 && cond2 && condN}>
{(() => {
const val1 = relatedToCond1!
const val2 = relatedToCond2!
const valN = relatedToCondN!

return <>...</>
})()}
</Match>
<Match when={cond1 && cond2 && condN}>
{(() => {
const val1 = relatedToCond1!
const val2 = relatedToCond2!
const valN = relatedToCondN!

return <>...</>
})()}
</Match>
it ain't pretty, although for now I feel it does a decent job. It captures in one place, and in close proximity to the Match, all related type assertions. The inline function makes it clear that something special is going on, and avoids having to create a new component and pass down all the props. Using <Show> made it difficult to understand that it was being used for type narrowing rather than business logic. Just came back to this and possibly this may be the best option yet: constructing a final value in the where's expression that gets passed as a non-null asserted value to the child render function,
<Match when={
cond1
&& cond2
&& condN
&& {val1: relatedToCond1, val2: relatedToCond2, valN: relatedToCondN}
}>
{(data) => {
console.log(data().val1, data().val2, data().valN); // They're all defined and non-null asserted
// return jsx
}
</Match>
<Match when={
cond1
&& cond2
&& condN
&& {val1: relatedToCond1, val2: relatedToCond2, valN: relatedToCondN}
}>
{(data) => {
console.log(data().val1, data().val2, data().valN); // They're all defined and non-null asserted
// return jsx
}
</Match>