{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. {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.