<Switch/>
and <Match/>
Components in React
I’ve been using Solid JS quite a while now. It’s a great alternative to React, especially for building lightweight widgets; it has a very similar syntax, but certain pitfalls with React are nicely sidestepped.
But it also comes with a selection of native components for handling control flow. Take, for example, the <For/>
element:
// React
<ul>
{list.map((item, i) => <li>Item {i}</li>)}
<ul>
// Solid
<ul>
<For each={list}>
{(item, i) => <li>Item {i()}</li>}
</For>
</ul>
It may be more lines of code but, to me, Solid’s component approach just looks nicer. Granted, the reason why such a component exists is actually to aid perforance, but if readability is a side effect, then that’s just a win-win.
Ternary Operators
I was working on a React project that had the following code, relating to displaying a special offer on a site (simplified):
{isNowBetween('2023-06-01', '2023-08-31') ? (
<SummerOffer/>
) : isNowBetween('2023-12-01', '2024-01-06') ? (
<ChristmasOffer/>
) : (
<DefaultOffer/>
)}
If it is currently Summer, show the Summer offer; if it is around Christmas, show the Christmas offer; else show the default offer.
This code isn’t terrible. However, in the original code, isNowBetween
did not exist as a function, and was instead a series of date comparisons; and the offer components were written inline. My first suggestion would have been to do as I have done here and split things out into components and variables.
But, another solution may be to leverage an idea from Solid: the <Switch/>
/<Match/>
component. In other words, to write it like this:
<Switch fallback={<DefualtOffer/>}>
<Match when={isNowBetween('2023-06-01', '2023-08-31')}>
<SummerOffer/>
</Match>
<Match when={isNowBetween('2023-12-01', '2024-01-06')}>
<ChristmasOffer/>
</Match>
</Switch>
Here, the code just feels more neatly laid out, and it’s easy to collapse cases in the code editor.
The problem is, such components don’t exist in React, so I ported over a basic version from Solid. Here it is:
import React, { useMemo } from 'react';
function Switch({ fallback, children }) {
const conds = Array.isArray(children) ? children : [children]
for (let i = 0; i < conds.length; i++) {
if (conds[i].props.when) {
return conds[i].props.children;
}
}
return fallback;
};
function Match({ props }) {
return props;
}
Caveats
What will become apparent, however, is that the React version on its own is no more performant than the ternary operator: the entire component will re-render if its parent updates, even if its props haven’t changed value. This happens because <Switch/>
’s props, fallback
and children
, are objects that are recreated each render, and thus fail the equality check, even if they contain exactly the same values. For some apps, this won’t be an issue, but if it is causing slowdown, it will need to be wrapped in a memo
; for example:
function App() {
const values = ['One', 'Two', 'Three', 'Four', 'Five'];
const [value, setValue] = useState(values[0]);
return (
<>
<select value={value} onChange={e => setValue(e.target.value)}>
{values.map(value => (
<option key={value}>{value}</option>
))}
</select>
{useMemo(() => (
<Switch fallback={<p>Shown in all other cases</p>}>
<Match when={value === 'One'}>
<p>Shown when value is One</p>
</Match>
<Match when={value === 'Two'}>
<p>Shown when value is Two</p>
</Match>
</Switch>
), [value])}
</>
);
}
Conclusion
I’m quite happy with this approach, and I can see myself using it in projects. Having to wrap it in a memo
does mean it loses some of its benefits from a readability standpoint, but it’s a nice alternative.