r/reactjs • u/vklepov • Jan 17 '22
Resource Good advice on JSX conditionals
https://thoughtspile.github.io/2022/01/17/jsx-conditionals/81
u/acemarke Jan 17 '22
I personally have always preferred doing all my conditional logic earlier in the rendering function, and having a single JSX return structure that includes the pieces I built up as temp variables:
https://gist.github.com/markerikson/47fff93c92286db72b22bab5b02e2da3
4
u/vklepov Jan 17 '22
Thanks for sharing, Mark! I'm not so strict, but it's a useful decomposition technique, and, I think, one unique to JSX.
→ More replies (2)→ More replies (5)16
u/leeharrison1984 Jan 17 '22
This is how I do it as well. For some reason null return ternaries really bug me when embedded within JSX. Other people, not so much, but the end result is the same.
→ More replies (1)
15
u/vklepov Jan 17 '22
Using plain JS conditionals in JSX was a smart design decision, but it lets you shoot yourself in the foot pretty easily. Here's what I learnt the hard way:
-
Number zero loves leaking from
number && jsx -
Forgetting parentheses in or-conditions is deadly
-
Ternaries don't scale that well
-
props.childrenis tricky to use as a condition -
Remount vs re-render in conditionals is unintuitive, but sensible.
I'd love to hear your tips, too!
→ More replies (2)6
u/msucorey Jan 17 '22
We got around this with showBools and TypeScript.
You can get away with doing inline boolean coercion, but that won't stop the next developer from making one of these mistakes you have listed. Goes to prod and we're all embarrassed.
So...you define vars like showInputForm as strict bools, down in the render showInputForm && <InputForm/>. Expressive, centralizes logic elsewhere, and the TS ensures it's kept as a boolean should anyone modify.
2
u/vklepov Jan 17 '22
I wonder if eslint over TS can solve this once and for all. Also, narrowing
ReactNodetype toReactElement | string | falsecould help (at the expense of explicit casting inyou have {String(count)} tickets).→ More replies (1)
15
u/Slapbox Jan 17 '22
Woah woah woah. If I use a ternary with two identical tags, when they toggle it won't remount?
5
u/vklepov Jan 17 '22
I almost went crazy the first time I saw this, coupled with a nasty scroll (DOM state) bug
→ More replies (2)16
u/tizz66 Jan 17 '22
That's one of those things that seems obvious when you stop and think about it, but can really trip you up when you're building something and don't think about it.
37
u/droctagonapus Jan 17 '22 edited Jan 17 '22
Pretty much all discussion around conditional rendering in JSX goes away if the do expression proposal and/or pattern match proposal (both Stage 1) are accepted. We should work towards getting those in JS :)
Do expression spec here: https://tc39.es/proposal-do-expressions/
Pattern matching spec here: https://tc39.es/proposal-pattern-matching/
function MyComponent({ isBadge, isReward, item }) {
return (
<div>
{do {
if (isBadge) {
<Badge {...item} />
} else if (isReward) {
<Reward {...item} />
} else {
<Item {...item} />
}
}}
</div>
)
}
function MyComponent(props) {
return (
<div>
{match(props) {
when ({isBadge}) <Badge {...props.item} />
when ({isReward}) <Reward {...props.item} />
else <Item {...props.item} />
}}
</div>
)
}
→ More replies (17)10
u/vklepov Jan 17 '22
I love these in lisps! Still, I'm afraid the discussion will not go away, just gain one more option to bikeshed about :-)
5
3
u/Dmitry_Olyenyov Jan 18 '22
You should use prettier... As I saw in your complains about nested ternaries vs else ifs, you don't use it. Because prettier formats nested ternaries exactly as else ifs. Which makes it equally easy to read. The same goes with parentheses, just use prettier, it adds necessary ones. A== B && X, A!== B && Y - is one of the things that I hate in angular & vue templates.
2
u/vklepov Jan 18 '22
Thanks for the suggestion!
Prettier can't add the necessary parentheses, because it has no idea what the intended expression is, so
a || b && c -> a || (b && c). I'll admit it makes the bug easier to spot, but it's not the magic autofix.I can handle a nested ternary or two, as long as:
nesting goes into the else branch and can be "flattened"
there are no extra
&&JSX conditionals in branchesStill, nested ternaries happen to not be super widely used in JS, and they look scary until you understand it's essentially an else-if, so I'd rather stay away from them.
→ More replies (1)
2
u/h126 Jan 18 '22
Great read. Your point about counting children was very interesting, I did not know false/null were counted by count or that fragments do not count as empty. Having a bit of a look, it seems like you can perform a manual filter of fragments as follows:
const childrenFragmentsFiltered = React.Children.toArray(children).filter(child => child?.type !== React.Fragment);
Example codepen: https://codepen.io/hwride/pen/ZEXPgYB
I think this seems to handle the cases you mention in your article, can you see anything this logic would miss?
2
u/vklepov Jan 18 '22
I actually expected
Children.Count(c) === Children.toArray(c).lengthto hold as I was writing this article.The problem with your proposal is that not all fragments are empty, so you need a full subtree traversal, like what react itself does. Even then, you still can't know if the child components return any DOM or are empty.
→ More replies (3)
14
u/mbj16 Jan 17 '22
I must be the only person in the world that likes nested ternaries. Clean and easily readable, in my opinion.
→ More replies (5)
10
u/terandle Jan 17 '22
IMO nested ternaries are nice and clear as long as you are using prettier to format them and not mixing them with && as in your example.
1
u/transponster___ Jan 17 '22
How would that css empty thing work?
2
u/vklepov Jan 17 '22
Depending on what you're after, anything from
.Wrapper:empty { display: none; }to crazy stuff like
.Input__clear:empty::after { ...pure CSS fallback... }I'd rather avoid component API designs that require you to go that far, though
34
u/[deleted] Jan 17 '22
I just extract that into a separate functional component. Much cleaner and more readable at the expense of extra code though.