Hacker News new | past | comments | ask | show | jobs | submit login
Advice on JSX Conditionals (thoughtspile.github.io)
98 points by fagnerbrack on March 9, 2022 | hide | past | favorite | 120 comments



protip: you can use an IIFE (immediately invoked function expression) to put whatever conditionals or logic you want inside JSX! I use this all the time and it's much nicer for more complex blocks:

    {(() => {
      switch(state) {
        case 'loading': return <Loading />;
        case 'ready': return <Component />;
        case 'error': return <Error error={error} />;
      }
    })()}


Waste of memory by creating new function objects on each render.


Micro-optimisations aren't useful. If you get to the point where the performance cost of inline functions is a bottleneck, you probably shouldn't be using React at all.


Maybe, but I just prefer to have a named function defined with a useCallback and just call it.


That's fine, but that's more of a stylistic preference than a performance concern.


Won’t V8 just optimize this anyway? I doubt there’s any real difference in memory usage.


I am not sure, it has a closure for the 'error' local variable.


Yeah probably best to move this to a function outside the render loop with useCallback


Oh, I like this. Thanks.


Hard disagree on nested ternaries, or rather chained ternaries, which are just as easy to read and reason about as chained if else statements:

  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;


Sure, when you're neatly using booleans with names that are three characters long, this is super easy to parse.

Now throw in long functions with multiple arguments, and all kinds of different access patterns and this ternary starts to look like a nightmare.


It's still often the cleanest way and I find myself doing something like this:

  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.


Try as I might, I have to reason through ternaries every time I encounter them. I think the = being so removed from what it's actually assigning, but without grouping parens, is what messes me up. Plus I always forget what the punctuation characters mean—if/elseif/else uses words, so I don't have to remember.


> I think the = being so removed from what it's actually assigning, but without grouping parens, is what messes me up.

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


Agree, the problem with if/else is that you end up having to consider the whole block plus usually a little context from just before it. I still find it much easier to read, unless very poorly written, while all ternaries slow me down every single time I read one, no matter how well-written. I struggle to parse them into ideas and words and to follow the order of events, seemingly no matter how many times I encounter them. They just feel wrong. All that implicit scoping crammed into one line, relying on memory and active searching to find the boundaries and then follow the order of events back "to the top", rather than having them explicitly marked.


Yeah, I think nested ternaries are fine as long as it’s just a list like OP is suggesting…

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


Just read whatever has a question mark after it with the intonation of a question, and it flows naturally.


While I do use this style occasionally, the previous commenter's point still stands that as value1, value2, fallback become longer than a couple of characters it does become very hard to read. It's also something that can start out fine but as the code grows more complex over time it can spiral into unreadability.


I have been writing code in C-like languages since 1996 and I find the ternary equivalent to be unreadable.

If you're ever doing this in any language with simple if() conditions, you need to refactor anyway.


https://rules.sonarsource.com/javascript/RSPEC-3358

Maybe, but sonarqube doesn't like it, which means I can't pass the CI pipeline, so...


Especially if you format them well. Just use a newline before each `:` and maybe after each `?` (if the value expressions are large), and it'll look great.

  return isA ? A
    : isB ? B
    : isC ? C
    : D


This looks fine as an example right here, but it starts to look terrible if there's larger or multiline boolean expressions involved, while if/else if winds up still being pretty readable.


I find this the easiest variation to read:

  return isA && A || isB && B || isC && C || D;
Am I weird?


Most of these are just "tips on using conditionals" and have very little to do with JSX itself.


True. Also many of these are not actual issues and are just behavior inherent to using JavaScript.

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.


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


I've always found these approaches to writing conditions in JSX to be terrible. Wouldn't it be nice if JavaScript were an expression-oriented language? Then you could just write:

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

[0] https://github.com/tc39/proposal-do-expressions


From the article:

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


Yes, I'm well aware of how to use these. I will elaborate on why I think it's terrible (even though there is no alternative in standard JS yet):

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


> Outside of JSX, using `&&` instead of `if` for control flow would raise eyebrows from most people, I think.

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.


> I'd honestly prefer a runtime error, just like you get if you try to render a JS object

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.


> The React framework strives for catching everything at compile-time. Runtime errors are a big no-no in web development.

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.


I get what you are saying. In my experience, it ends up being moot when you are explicitly trying to avoid runtime errors, because you’ll need something along the lines of “guard() && <Component />” or you could simply have “<Component />” and then within Component render have “if (!guarded) return <Fragment />”, etc.

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.


You can add do-expression proposal support in .babelrc:

    <div>
    {do {
      if (user) {
        <Logout />
      } else {
        <Login />
      }
    }}
    </div>
https://babeljs.io/docs/en/babel-plugin-proposal-do-expressi...


Or, no need to enable anything:

  {user
      ? <Logout \>
      : <Login \>
  }


Yes, my example is trivial, but if-else can easily outgrow the ternary. The do-expression lets you embed arbitrary statements.


Ok this thread just reached peak JavaScript.


Ternary expressions have been around long before JavaScript was a twinkle in Brandon Eich’s eyes.


Just modern ECMA Script, not peak JavaScript. ES6 and React Hooks have fully sublimated the web development landscape.


It’s generally better to go with the idioms of the language, sure, but in this case, the idioms of JSX work well and they are future-proof. If JavaScript adds the support that you are looking for, it would be easy enough for a static code analyzer to rewrite “&&” as “if”


Why do you think of JSX’s use of && as control flow?


Can’t speak for GP, but for me, because:

    { someBool && <Anything /> }
… only renders <Anything /> if someBool is true.


What if you consider it as ‘only returns a data structure that requests the rendering of <Anything /> if someBool is true’?

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.


I already responded down thread because I didn’t see this comment first. But I’ll add that your console log example is more analogous to the JSX control flow question as:

    someBool &&  console.log(“anything”)
Hope this helps.


and if that expression-body is wrapped within the render() method.


Okay: only evaluates <Anything /> if someBool is true. The value of someBool quite literally controls which code path is taken. It’s even more clear with a fallback:

    { 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 />;


But the short circuiting doesn’t matter.

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.


Edit: I read this back to myself and realized it may come off as harsh or mean. I don’t intend it as such, and I apologize if it’s received that way. I think being precise about this is worth pursuing, for the sake of both developers thinking about it and users experiencing what we build.

- - -

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?”


> 1. Execute the code (duh), which at minimum uses CPU resources

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.


I'm sorry, but you're just mistaken about how JSX works.

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


> I'm sorry, but you're just mistaken about how JSX works.

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.


You said that “evaluating a component when you don’t intend to use it” will do a bunch of things which React manifestly doesn’t do. I shared a link to some code you can run that demonstrates this. The question of whether or not some code gets executed is, as you said, knowable.

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.


> 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

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.


> It doesn’t matter where the expression is placed, it’s the same if you assign it to a variable

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.


I’m not sure whether I agree or disagree with this. But I do want to clarify that my intent was not to argue for or against conditional expressions in any code position, only to address the factual question of whether they constitute control flow.


Another quick option for avoiding the "zero" issue:

  {!!number && <Thing />}


Better to use Boolean(number)

Its more obvious what it achieves just at a glance.


"!!" should be pretty obvious to any javascript developer.


Nonetheless, better for the industry if we start to embrace more uniformity in our idioms.

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.


What if you wanted to get the boolean value of number but with not.

Would you write: !Boolean(n)

Or would you write: !n


if it were C#, I’d write it along the lines of !(n as bool), but for the purposes of JS, I suppose something like:

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.


It might be, but not everyone knows js and I don't expect future people to be me.

More obvious and more readable is always better


One of the joys of JavaScript, right there: not-not sorry.


Yes Dart with Flutter and swift UI both had to add language features just to support if and loop expressions. But people writing immediate mode guis have been doing the exact same thing using standard language features for years. There's no reason you can't mix React-ive style components with an immediate style API. I do this in a GUI library I've written in D. The downside is you get slightly less type safety, but I'm happy to pay that cost


> swift UI both had to add language features just to support if and loop expressions

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.


For these situations I often use IFFEs with if-statements and early returns inside.


It doesn’t necessarily have the same benefits in React, but folks ought to consider using components for control flow, like SolidJS does[1].

1: https://www.solidjs.com/docs/latest/api#control-flow


Agreed. I particularly like the look of their `<Switch>/<Match>` component[1]:

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

[2] https://github.com/AlexGilleran/jsx-control-statements

[3] https://github.com/samuelneff/react-control-flow


You could very easily implement that pattern yourself in a react app as a few HoCs. So far I think the react project has done a pretty decent job at limiting the surface area of the API. This seems a bit high level.


Funnily enough, this has existed in React through a Babel plugin. I'm not sure why this hasn't really caught on.

https://github.com/AlexGilleran/jsx-control-statements


I’ve encountered that, and kind of don’t understand why you’d need a Babel plugin. They’re trivial components to implement in regular JSX.


That's what I was thinking too!


Control-flow components like "<For>"... because you couldn't stand just writing "for" or "if" like a caveman and had to invent a custom DSL for it.


Solid actually uses them for static analysis which ~isn’t otherwise possible in JSX, which has to be an expression. It would be great if for/if were expressions in JS, but they’re not.


Some people consider this added complexity?

https://news.ycombinator.com/item?id=30509806


I mean, sure. But compilers often impose complexity as a trade off for performance or other benefits.


That's pretty slick.


I've always much preferred using local variables to hold conditional fragments. Since React will safely ignore null/undefined, you can do:

  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.


I do this sometimes but having to look up where child was defined and everything that could have changed it can be tiresome.

That's why a function can be better, at least you know that the value isn't changed somewhere unexpected.


Thanks, this is really useful.

I’ve been using this Babel plug-in found it quite intuitive

https://github.com/AlexGilleran/jsx-control-statements


Drives me nuts that there's no straightforward way to do a switch statement in JSX.

  <Layout>
    <Header />
    {switch (page) {
      case 'home': return <Home />
      case 'about': return <About />
      default: return <NotFound />
    }}
    <Footer />
  </Layout>


I'm surprised this doesn't work since you could easily externalize this to a function:

  <Layout>
    <Header />
    {getCorrectComponent(page)}
    <Footer />
  </Layout>

  const getCorrectComponent = (page) => {
    switch (page) {
      case 'home': return <Home />
      case 'about': return <About />
      default: return <NotFound />
    }
  }


Yeah, this is generally a good compromise. I sometimes like to write these as helper components instead:

  <Layout>
    <Header />
    <Content page={page} />
    <Footer />
  </Layout>

  const Content = ({page}) => {
    switch (page) {
      case 'home': return <Home />
      case 'about': return <About />
      default: return <NotFound />
    }
  }


that does work. imo ugly, not straightforward


You could of course wrap the switch in a self-invoking function: {() => { switch... }()}

Sometimes an inline switch is indeed the clearest code. The closest JS has is the do-expression proposal: { do { switch... }}


The example you put forward could be resolved through using a router. The router implementations are essentially fancy switches without ”switch”.


yeah, I use reach/router for this kind of thing. A page switcher was just the first example that popped into my head.

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.


I'd write it like this

  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>
  )}


That’s definitely representative of the compactness that is possible with modern JSX.


I've learnt most of those the hard way, event though TSX is a great safe guard because I tend to slip.

I find funny the persistance in sidestepping a `.length > 0` at all costs.


Personally I hate implied casting of booleans (and implied casting generally), and go out of my way to make boolean checks explicit whenever I touch the code. These kinds of shortcuts made some sense when minification was commonly a manual thing. But now, I want code to be as verbose as necessary for it to be unambiguous.


I think a better approach is to move the logic to a renderXxx() function:

    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)}


This pattern is fine, but I would make this a component.

    function ConditionalComponent(props: Props) {
      if (!props.condition) return null
      return <UnderlyingComponent {...props} />
    }


Though you're also adding indirection.

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.


How is that indirection? These are semantically identical:

    {renderIf(foo)}
    <RenderIf foo={foo} />
The latter is a bit more verbose, sure, but it’s both more idiomatic and a better optimization target.


When logic gets more complicated than a single ternary (e.g., handling pending/error/result state), I've found that a `useMemo` gives me the flexibility I need. It also keeps the messy logic out of the final JSX, which tends to make it read better.


The React maintainers did put a lot of effort into making hooks. The variety of hooks seem to cover all the use cases for programming.


I've personally rarely even needed to useMemo, I just write a function that returns the JSX I need and { myFunction() } in the functional component's return (given React).


If you're using Typescript that can get a bit annoying, since you'll also need to pass in whatever props necessary for the function to handle the logic + type them. Makes it nice and easy to test in isolation though.


Typically the function has access to whatever props or state it needs because it's in the scope of the component. It's not pure, but it's rare that I need to verbosely pass props into the function call and then accept them in myFunction. I don't typically test these functions in isolation as the component itself is pure and I test the output of the component as a whole instead.


Oh gotcha, I thought this was a function that was defined outside of the component.


Great article!

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 just avoid most of it by moving the complex conditional logic into a separate component and rendering that.

I don't think I've run into many issues with this strategy, and keeps my return statements nice and clean.


I find ternaries alone to be totally adequate.


Oh they work, but stacking them tends to make future reading of the code harder due to their increased complexity.


Nested ternaries can by structured to look a look like `if/else` if you put the `?/:` in the right spots.

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


Agreed, if it's more complex than a single ternary, or && statements, make a new component.


IMHO this is an anti-pattern.

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.


The ideas described here are just tools and the article contains plenty of simple and readable examples of the tools being used well. If you end up using them to create giant unreadable blocks of JSX then you're using the tools wrong, but that doesn't make the tools themselves an anti-pattern. The anti-pattern there is "giant unreadable blocks of code".


I'm assuming it dates back to class components and Class per File ideologies


Interesting! And yeah Class per file had the exact same problem - classes were big and did too much. And now everyone hates "OO"


There's a babel plugin for adding an Angular-style "display-if" to JSX - https://www.npmjs.com/package/babel-plugin-jsx-display-if

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


Nice article.

To me it boils down to one JS problem: Lacking `if` expression, and one React problem: Side effects of un/mounting.


The more conditional logic you have in a component, the harder it is to write tests for.


Agreed. This is also true for functions and especially true for stateful classes/objects.


I actually really like the nested ternaries. TypeScript understand them very well and you don't have to extract your code outside the render to start using if/else structures. They also format quite well.


I like ternary conditionals, nested or otherwise. But I also like reduce. And regular expressions. And I know a lot of other devs hate some or all of them. So I tend to use them sparingly when working with a team… although I do see more ternaries in idiomatic code these days, generally as devs embrace a more FP style.


its rare but im ok with a nested ternary when its being passed down as a prop. dont wanna use a function in that situation.

sidenote - I discovered vladimirs blog a while back and love it. high quality content


I missed a v-if in react/JSX. My code is so hard to read in JSX.


This is usually a good indicator that your components may be doing too much. If your component’s JSX specifically is hard to read, you probably have multiple components implemented within one.




Join us for AI Startup School this June 16-17 in San Francisco!

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: