Advice on JSX Conditionals
thoughtspile.github.io
thoughtspile.github.io
{(() => {
switch(state) {
case 'loading': return <Loading />;
case 'ready': return <Component />;
case 'error': return <Error error={error} />;
}
})()} if (isA) {
return A;
} else if (isB) {
return B;
} else if (isC) {
return C;
} else {
return D;
}
is equivalent to return isA ? A : isB ? B : isC ? C : D;Now throw in long functions with multiple arguments, and all kinds of different access patterns and this ternary starts to look like a nightmare.
let x = somelongfunc(arg, blah(etc))
? value1
: some other condition
? value2
: fallback
What I usually want is pattern matching expressions, but those are not in many languages.I almost always use grouping parentheses for this reason, unless it’s a very short single line expression. That said, if/else if/else has a different colocation problem: it puts the assignment further from the initial declaration, making its scope less obvious (unless you’re hoisting var, which is awful for its own reasons).
But it breaks peoples brains for some reason so I rarely use them professionally.
It’s really not that hard though, the ? and : are just shorthand for “else if” and “then”.
If you're ever doing this in any language with simple if() conditions, you need to refactor anyway.
return isA ? A
: isB ? B
: isC ? C
: DMaybe, but sonarqube doesn't like it, which means I can't pass the CI pipeline, so...
return isA && A || isB && B || isC && C || D;
Am I weird?If you don’t like your conditional logic in your template just give them a name and explicit boolean type and be done with it.
Still a useful overview of common idioms though for people who just get started.
I agree - I just think labeling them as JSX issues is pretty disingenuous. JSX is literally just running them as standard js expressions here - which I actually find delightful: they work the same as they do in normal javascript (quirks included :D ).
The author seems to want something like ng-if="condition", and I find DSLs in this space a real abomination.
{if (gallery.length) {
<Gallery slides={gallery}>
}}
There has been a "do expressions" proposal [0] for many years, which addresses this (though it is more verbose). I hope it will be accepted some day.“{number && <JSX />} renders 0 instead of nothing. Use {number > 0 && <JSX />} instead.”
Functional JSX would look like:
const isNumber = number > 0;
{isNumber && <JSX />}
You can similarly do things like:
const isVisible = condition1 && (condition2 || condition3) || guard(props.input1);
{isVisible && <JSX />}
The functional paradigm and some basic code factoring can make quick work of conditional JSX
* It encourages JSX-specific idioms. Outside of JSX, using `&&` instead of `if` for control flow would raise eyebrows from most people, I think.
* I find it easier and faster to refactor `if` statements to `if/else` and vice versa (only requires addition or deletion of code) than to refactor `&&` to a ternary operator and vice versa (also requires modifying existing code).
* Multiple nested ternary operators almost immediately become a mess, while a series of `else if` expressions (if such a thing existed) seem perfectly readable.
<div>
{do {
if (user) {
<Logout />
} else {
<Login />
}
}}
</div>
https://babeljs.io/docs/en/babel-plugin-proposal-do-expressi... {user
? <Logout \>
: <Login \>
} { someBool && <Anything /> }
… only renders <Anything /> if someBool is true. { someBool && <Anything /> || <Fallback /> }
Which would more idiomatically be written as a ternary conditional, but still. It doesn’t matter where the expression is placed, it’s the same if you assign it to a variable: const el = someBool && <Anything /> || <Fallback />;
Or even just as an expression statement: someBool && <Anything /> || <Fallback />;If you separate the concern of the condition definition from the conditional rendering, by pulling the conditional’s definition into a variable, you do get enhanced portability, though.
Much easier to port between languages and frameworks if your conditional definition can be copy-and-pasted out without having to mess with untwining the previous developers expression statements.
You could go
let x = <Anything />;
return someBool && x;
And the result (assuming well behaved react code) would be the same. <Anything /> is just a literal expression. It doesn’t matter whether it gets evaluated or not.You’re not using && shortcircuiting to prevent <Anything/> from being evaluated - it doesn’t matter if it gets evaluated. You are using it to decide which value to return.
This really isn’t ‘using shortcircuiting for control flow’. It’s just using && as an operator in an expression evaluation.
- - -
Of course it matters.
“Well behaved react code” isn’t side-effect free, as much as it tries hard to look almost like it is. You can write perfectly idiomatic components which follow all best practices, and evaluating a component when you don’t intend to use it will still:
1. Execute the code (duh), which at minimum uses CPU resources
2. Perform relevant hooks and call lifecycle methods, including those which have side effects (hint: “effect” is in the hook’s name, for this reason): starting timers, loading resources, calling non-React APIs, pretty much anything goes by design, even if you follow all the rules and other best practices.
3. Prepare for a new, potentially concurrent, render. This may speculatively interrupt/pause a render in progress which would otherwise complete without interruption. Which may, in turn, cause seemingly unrelated components to be called again, cascading this entire set of potential side effects to them, and so on.
4. Call any components in that cascade whether they’re well behaved in your control, or epic shit show dependencies.
Being in an expression position does not mean it’s side effect free. If you don’t believe me, run this code:
const isPure = (value) => (
value = 'NOPE',
console.log('There is no code block in sight, this function has zero statements. Is it pure?', value),
value
);
isPure('yeah?');
And sure, React and most JSX implementations are designed to make evaluating JSX side effects local as much as possible. But that’s limited because it’s exposed to APIs not under its control by design, first of all. And more importantly, I would hope that, on HN of all places, a knowable answer to a factual question “is code executed?” is not treated as subjective. This is the exact same question as “is it control flow?”In this case it really is quite a minimum use of CPU, as React's createElement function doesn't do a whole lot. As far as I know, in a production build it's really just going to do a teeny amount of work to create an object (which looks like {type: Login, props: {}, /* ...some other properties */}). "Evaluating a component" isn't something the user ever does directly. The React runtime is the only thing which ever invokes a component's render method.
> 2. Perform relevant hooks and call lifecycle methods, including those which have side effects (hint: “effect” is in the hook’s name, for this reason): starting timers, loading resources, calling non-React APIs, pretty much anything goes by design, even if you follow all the rules and other best practices.
> 3. Prepare for a new, potentially concurrent, render. This may speculatively interrupt/pause a render in progress which would otherwise complete without interruption. Which may, in turn, cause seemingly unrelated components to be called again, cascading this entire set of potential side effects to them, and so on.
> 4. Call any components in that cascade whether they’re well behaved in your control, or epic shit show dependencies.
It won't do any of these things. React won't try to actually render a component (call its render function) unless an element referring to it ends up being returned by the render function of another component which gets rendered.
<Foo /> does not translate into a call to Foo(). It translates into React.createElement(Foo).
And Foo will only be called if the element returned from that call winds up being examined by some renderer like ReactDOM, mounted as a component, and actually rendered.
Here:
function UnrenderedComponent() {
console.log("UnrenderedComponent does not get executed");
React.useEffect(() => {
console.log("this effect never runs")
});
return "This won't get rendered"
}
function RenderedComponent() {
console.log("Rendered Component running")
React.useEffect(() => {
console.log("this effect runs")
});
const x = <UnrenderedComponent/>;
return "This will get rendered";
}
ReactDOM.render(
<RenderedComponent />,
document.getElementById('container')
);
I'll save you copy/pasting - here's a jsFiddle: https://jsfiddle.net/gr4kxq81/That code unconditionally includes the evaluation of <UnrenderedComponent/> in the context of a render method.
But the UnrenderedComponent() function does not get called, its useEffect call never happens, its effect code never gets executed, because the resulting element doesn't make its way back to ReactDOM to be mounted.
someBoolean && <MyComponent/> is not control flow, it's a simple expression that conditionally evaluates to an instruction to either not render anything, or to mount and render a MyComponent. It's cheap and side effect free.
My comment about 'assuming well behaved React code' was more directed at the fact that technically you can plug in a custom JSX pragma so you might not just be handing it to React.createElement, in which case all bets are off for what happens. But React doesn't call render methods for components unless they're mounted.
No, I’m not. JSX doesn’t do anything. But it’s an expression and evaluated according to the rules of JS expressions. You might expect React or whatever to evaluate it one way today, and it can be evaluated another way tomorrow. React.createElement isn’t the API you’re using, neither is react/jsx-runtime. You’re using an expression with some special angle brackets and curly braces. You have no idea if your component is being called, but semantically you have every reason to believe it is and will be.
Bool && otherExpression is control flow because it’s specifically defined as an expression in the host language. Anything else is assuming compiler magic you were never promised.
But you are, I think, retreating to a position that, because the JavaScript && operator shortcircuits (ie does not evaluate the second operand of the first operand is truthy) it is always a control flow - a point which I can sort of agree with, but which I think is just a weak position, since it doesn’t help us make decisions about what code is good or bad.
Because if we back up to the top of the thread the point being raised was that use of && for conditionals in react feels like using && to perform control flow, and that is idiomatically bad JS.
But you can’t have it both ways - if using && for control flow is bad and && is always a control flow operation then every use of it is bad. But if there are some times where && use is good, then there must be cases where using &&, even though it shortcircuits, does not count as using it for evil control flow.
But even in simple idiomatic JavaScript we rely on && shortcircuiting for good and valuable purposes like nullsafety:
if (person !== null && person.name === ‘foo’)
So is that using && for control flow, which is bad?What about here?
return person && person.name;
Or return person && `Name: ${person.name}`;
.. or even return person && <Person name={person.name}/>
(Notice what I’m guarding against here is not against incorrect evaluation of the Person control - I’m guarding against incorrect evaluation of one of its parameters)My position is, && shortcircuiting is a feature and you can use it as such, but if your code relies in a nonobvious way on the shortcircuiting behavior for correctness, then you are using && as a control flow tool, and you have made it brittle, harder to refactor, and harder to parse.
But if your code would still be correct even if JavaScript did not guarantee that it would not evaluate the second parameter if the first was truthy, then you’re not even relying on shortcircuiting, and using && is clean and valid.
And since JSX literals in react are really just shorthand for a special kind of object literal, and in general therefore it doesn’t matter if they are evaluated or not, I take the position that using them within JS && expressions is legitimate, and not an abuse of && to perform complex control flow.
I’m not retreating to anything. This was my only point and I explicitly said so.
> a point which I can sort of agree with, but which I think is just a weak position, since it doesn’t help us make decisions about what code is good or bad.
I’m not trying to change the world with the point. Just to establish the basic fact.
If you write
console.log(someBool && “anything”)
Are you altering the control flow because the console window will call a different bit of font evaluation code to render “anything” instead of “false”?Or are you just conditionally evaluating an expression that results in different data that causes different downstream effects?
Evaluating <Anything/> doesn’t do anything. It doesn’t make any DOM elements. It doesn’t trigger any useEffects. It just returns an object that has the potential to be hooked into a react renderDOM lifecycle to provide further instructions on what the DOM should look like and what other core should run.
someBool && console.log(“anything”)
Hope this helps.Very much so, at least for me. Relying on the short-circuiting of logical operators is fine, but only when you're actually going to use the resulting value. In the case of JSX, this is relying on the fact that `false` is a valid React child which renders nothing. Not only does this result in a mistake when the `&&` expression returns something like `0` that is falsey but isn't `false`, IMO it's already pretty awkward even if you are rendering `false`. I'd honestly prefer a runtime error, just like you get if you try to render a JS object, and only support rendering null and maybe undefined as React children.
The React framework strives for catching everything at compile-time. Runtime errors are a big no-no in web development.
If I recall correctly, rendering null is behaviorally equivalent to not rendering, in React.
I don't know whether that principle is generally true or ought to be generally true, but React does throw a runtime error if you render a plain JS object as a React child. This can probably also be prevented at compile time with linters or TypeScript, but given that React has to do something at runtime if it encounters an invalid child, I think throwing an error is preferable to just rendering nothing or having some undefined behavior.
In my opinion, rendering `null` is a pretty clear and explicit way to indicate you don't want to render anything. But rendering `false` (or `true`, for that matter) is not at all so clear to me. I think throwing a runtime error would be better, and would largely make the `thing && <Component />` idiom go away.
At that point, you’ll probably need to worry about component collections containing empty elements, though. That pulls you back into the parent scope, anyways.
There’s probably a nicer way to handle it with custom hooks, though.
> I don't know whether that principle is generally true or ought to be generally true
They sure do go out of their way to make misuse of hooks a compile-time error. I think that those useful error messages go a long way to rectifying the archaic semicolon error messages of the C days.
{!!number && <Thing />}Its more obvious what it achieves just at a glance.
You can practically copy-and-paste between JavaScript and C# these days, with some trivial text replacement tweaks, if you are careful with your idioms.
Would you write: !Boolean(n)
Or would you write: !n
const IsNumber = (value) => Boolean(value);
!IsNumber(n)
I’m not a fan of using the return-type as the function name, especially when you are really just trying to find out if something is a number.
More obvious and more readable is always better
The special syntax for loops in SwiftUI is important, because plain-old loops are always eager. For example, if you were building building a list using a loop, it would iterate over every item up-front to generate the list's body. With the `ForEach` struct, on the other hand, you provide the block to create each item, and it can be invoked lazily as the content will appear.
<Switch fallback={<div>Not Found</div>}>
<Match when={state.route === "home"}>
<Home />
</Match>
<Match when={state.route === "settings"}>
<Settings />
</Match>
</Switch>
Which doesn't seem to have an analog in the React babel-plugin[2] or standalone lib[3][1] https://www.solidjs.com/docs/latest/api#%3Cswitch%3E%2F%3Cma...
let child;
if (someCondition) {
child = <SomeComponent ... />;
}
return (
<div>
Maybe here's a child: {child}
</div>
);
This lets you avoid embedding conditional logic in your (already pretty dense) JSX tree.That's why a function can be better, at least you know that the value isn't changed somewhere unexpected.
I’ve been using this Babel plug-in found it quite intuitive
<Layout>
<Header />
{switch (page) {
case 'home': return <Home />
case 'about': return <About />
default: return <NotFound />
}}
<Footer />
</Layout>Here's a real world example if it matters…
const Grid = (gridItems) => (
<Grid>
{gridItems.map(item => (
<GridItem {...item} key={item._key} />
_}
</Grid>
)}
const GridItem = (props) => {
switch(props._type) {
case 'image': return <GridImage {...props} />
case 'video': return <GridVideo {...props} />
case 'copy': return <GridCopy {...props} />
// slideshows, 3D stuff, newsletter signup forms…
default: return <></>
}
}
… and I suppose a case could even be made for the granularity of this superfluous <GridItem /> component… but regardless this is just a minor annoyance that comes up from time to time. const gridComponent = {
image: GridImage,
video: GridVideo,
copy: GridCopy
}
const Grid = (gridItems) => (
<Grid>
{gridItems.map(item => {
const GridComponent = gridComponent[item._type]
return GridComponent ? <GridComponent {...item} key={item._key} /> : null
}}
</Grid>
)} <Layout>
<Header />
{getCorrectComponent(page)}
<Footer />
</Layout>
const getCorrectComponent = (page) => {
switch (page) {
case 'home': return <Home />
case 'about': return <About />
default: return <NotFound />
}
} <Layout>
<Header />
<Content page={page} />
<Footer />
</Layout>
const Content = ({page}) => {
switch (page) {
case 'home': return <Home />
case 'about': return <About />
default: return <NotFound />
}
}Sometimes an inline switch is indeed the clearest code. The closest JS has is the do-expression proposal: { do { switch... }}
I find funny the persistance in sidestepping a `.length > 0` at all costs.
function renderInput(props:Props) {
// Early exit if props are not as expected
if (!props.cond1) return null;
return <JSX/>;
}
Then the parent markup is much cleaner, without any conditional: {renderInput(props)}Not always worth it when you're just trying to do some conditional logic next to the code/components that it's related to.
Ideally we have the tools to decide when to add indirection ourselves instead being forced to do it to deal with complexity. You could also see this in callback-hell when we'd flatten callback trees with indirection—the tree looked flatter in the editor but we just moved code around. async/await gave us the tools to decide when we actually wanted it.
{renderIf(foo)}
<RenderIf foo={foo} />
The latter is a bit more verbose, sure, but it’s both more idiomatic and a better optimization target. function ConditionalComponent(props: Props) {
if (!props.condition) return null
return <UnderlyingComponent {...props} />
}I will say I've found that using prettier makes nested ternaries much more readable.
I couldn't imagine using them without prettier, but since every app I work on these days uses prettier nested ternaries aren't so bad.
I don't think I've run into many issues with this strategy, and keeps my return statements nice and clean.
It's a balance. Extracting logic into new components (and often new files!) isn't exactly ergonomic either.
I've found it's better to decrease readability if it makes understanding the logic more straight-forward (i.e. not hunting through several files to exhaust all possible outputs).
This involves people writing giant blocks of JSX that are hard to read. Especially with && all over the place. Just split up your component into smaller components with max one conditional each.
Related, can anyone tell me the origins of the bizarre react practice of one file per component? I'm assume some mega corp told people it was "best practice", but the end result is way too many files with hard to read components.
I've only seen it used in projects once or twice but it really does wonders for readability once the team becomes OK with the new-ish syntax
To me it boils down to one JS problem: Lacking `if` expression, and one React problem: Side effects of un/mounting.
sidenote - I discovered vladimirs blog a while back and love it. high quality content