3

4 Ways to Render JSX Conditionally in React

 1 year ago
source link: https://dev.to/rasaf_ibrahim/4-ways-to-render-jsx-conditionally-in-react-2bal
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

4 Ways to Render JSX Conditionally in React

In this extremely limited example, I’d agree that boiling the component down to a single ternary is fine. Most real world components however will contain more than a single truthy test and a single line of JSX in their return.

Guard clauses (or early returns) are great for refactoring nested conditional trees. Should you use them for every component? No. Does it meet the author’s post criteria of “Ways to Render JSX Conditionally in React”? Yes, I’d say so.

I’m not familiar with “scrape returns”, and I can’t seem to find any examples online. Can you elaborate, please?

Thread

Thread

They said, "scape" not "scrape" so I'm assuming it's either a typo for "Escape" or slang for it.

I can see how "escape" could sound a bit cooler than "return early" :)

Thread

Thread

Thanks, @moopet! Complete misread on my behalf! 😅

Thread

Thread

Is not "scrape" is "escape" (I had a typo and forgot the "e") and I put that in quotes because I was just saying I don't like to use return as a way of "going out/escaping" of a function. Generally if you need an early return, that's a sign that you're putting too much complexity in a single function. You end up sacrificing readability to reduce nesting, instead of actually addressing the issue and splitting the logic.

Thread

Thread

If you use a conditional return at the beginning of your function you can prevent everything else to execute, useEffects, useStates, whatever custom function and so on so this pattern is very used TBH, can't see the issue with it, it's pretty understandable, and useful in tones of different architectures. 🤷🏻‍♀️

Thread

Thread

That's my point when I say that needing an early return is a sign that you actually need to simplify that function. Maybe all those hooks should be in another place, like a custom hook or another component (which most definitely would help readability more than putting returns all over the place).

Thread

Thread

You'll need to USE this custom hook anyway inside your component, isn't it?

Those are two different things, you can simplify as much as you can the functions, apply SOLID into them and extract functions from components, but a component itself will probably need any hook (being custom or not) and some presentation logic.

It's common to use this pattern in Next JS, specially when you use SSR to avoid client-server missalignments:

if( !someProp ) return null;

While it's not so common in React, is also perfectly valid as defensive programming technique when a prop is required for a component to render properly.
It's placed at the beginning of the component, pretty understandable and suits any case in which a default prop value is not beneficial or, if you've preference for other patterns, we can say it's beneficial whenever a ternary would be worse.

Also consider that:

function Test({ userName }) {
  return userName && (<p>hi {userName}!</p>);
}

Using this pattern (or similar ones) we receive undefined as return value whenever Test component either doesn't receive a prop or the prop evaluates to falsy.

So if you do something like:

/**
 * Returns a message depending on the quest result
 * @param {boolean} winner
 */
function QuestResult({ winner }) {
  return winner && (<p>You {winner ? 'WIN' : 'LOSE'}!</p>);
}
export default function Whatever() {
  return (
    <div className="results">
      <h1>Quest Finished</h1>
      <h2>Result</h2>
      <QuestResult winner={false} />
    </div>
  );
}

You'll get no message if you lose because winner is false, so it can cause non desired side effects.
So it's better to stablish that pattern as default one (from my experience):

/**
 * Returns a message depending on the quest result
 * @param {boolean} winner
 */
function QuestResult({ winner }) {
  if( typeof winner !== 'boolean' ) return null;
  return <p>You {winner ? 'WIN' : 'LOSE'}!</p>;
}

Of course you can do the same condition with a ternary:

/**
 * Returns a message depending on the quest result
 * @param {boolean} winner
 */
function QuestResult({ winner }) {
    return typeof winner !== 'boolean' ? <p>You {winner ? 'WIN' : 'LOSE'}!</p> : null;
}

but some components grow inevitably so they will become less readable plus this way you can control the condition easily and not having it coupled to the main return of the component. Easier to spot in PRs also!
Just look at this super simple example, we get a nested ternary here.
Is it worthy to decouple the message on a function getResultMessage(winner) or is better to use the other pattern?
And we even considered the hooks in the example above.

export default function MyComponent(props) {
  if( !props ) return null;
  // effects, states, whatever
  return <Whatever props={props} />
}

In this case nothing will be executed if props is not set (or falsy)

export default function MyComponent(props) {
  // effects, states, whatever
  return  props ? <Whatever props={props} /> : null;
}

In this one they will be (What a waste, huh?)

Thread

Thread

It's common to use this pattern in Next JS, specially when you use SSR to avoid client-server missalignments.

If you do:

if (!someProp) return null;

useHooks();

return <Example someProp={someProp} />;

It's the same as doing:

if (!someProp) {
    return null;
} else {
    useHooks();

    return <Example someProp={someProp} />;
}

Both have the problem that you're optionally calling hooks, so both are going against the rules of hooks. So in either case you need to call hooks first and then return, so the actual "versus" is:

useHooks();

if (!someProp) return null;
return <Example someProp={someProp} />;

// vs

useHooks();

if (!someProp) {
    return null;
} else {
    return <Example someProp={someProp} />;
}

// or ...

useHooks();

return someProp : <Example someProp={someProp} /> : null;

So in the case of hooks, the early return is not helping at all, is pretty much the same, just with a different style (which I stated before, I like less than just using a ternary).

While it's not so common in React, it is also perfectly valid as defensive programming technique when a prop is required for a component to render properly.

I was talking about "defensive programming" when I said that you should take that as a sign something should be worked on. If you need to avoid passing something because it could break stuff, then maybe make that stuff more stable? The previous example could avoid the "if" altogether if the Example component was able to receive someProp optionally, but because is required we had to write all that "defensive" code.

Using this pattern (or similar ones) we receive undefined as return value whenever Test component either doesn't receive a prop or the prop evaluates to falsy.

I've already said it in several comments, but I'm not in favor of short-circuiting (not just in JSX, but anywhere). It's hacky code and it should be avoided. A ternary is my default solution for code like that, but also I use default values or nullish coalescing when needed:

const Test = ({ username = "Guest" }) => <p>Hi, {username}!</p>;
// or
const Test = ({ username }) => <p>Hi, {username ?? "Guest"}!</p>;

Also that QuestResult component has a short circuit just because of reasons, it should be written like this:

/**
 * Returns a message depending on the quest result
 *
 * @type {import("react").FC<{ winner?: boolean }>}
 */
const QuestResult = ({ winner = false }) => (
    <p>You {winner ? "WIN" : "LOSE"}!</p>
);

So you don't need to do an early return there, neither you need the nested ternary. As I said, when you think: "Hum! I could use an early return here", that generally means you need to fix something else. You might argue "but, my function is rendering that p depending on if winner was passed as a boolean or not", and my argument would be: If that is optionally rendered, then you should have other property for that (like hidden or visible) so that you don't have two unrelated pieces of logic (showing the message, and the content of the said message) coming from the same prop.

Your last example I addressed at the beginning of this comment, but with either approach, you should always run effects, so early return doesn't solve much (it might even potentially cause a bug).

Early return are almost always presented as a really "cool hack", but is only one style for doing things that, at least from my point of view, only makes the flow of the function less readable with no real benefits.

Thread

Thread

Really good points you bring up.

Just on one of them:

I was talking about "defensive programming" when I said that you should take that as a sign something should be worked on. If you need to avoid passing something because it could break stuff, then maybe make that stuff more stable? The previous example could avoid the "if" altogether if the Example component was able to receive someProp optionally, but because is required we had to write all that "defensive" code.

In most of the projects I work on, I need to do a lot of “progressive enhancement” on more monolithic systems (which inherently come with a number of restrictions/limitations/requirements). I’m often having to server-side render component data as Objects on the window global and then ingest them when my components conditionally render. Because I can never guarantee the stability of that data, I sometimes (not all the time) need to rely on the early return pattern to ensure the stability of the component.

Sometimes it’s because a CMS author has used a few unescaped characters and broken the Object (which we always do our best to sanitise, regardless). Other times a regression issue might cause the data Object to be malformed when it renders. But due to the nature of the environment, there is sometimes no way to make the data source “bullet proof”.

In my experience, when you can’t guarantee the existence and stability of your props, the early return pattern helps to maintain the stability of your components.

Thread

Thread

Oh yup, at work we have to deal with some unstable APIs quite often. We usually have types that reflect that (pretty much everything is Partial because it could be there or not), and make great use of optional chaining and nullish coalescing. The other tools that are great for flaky data sources are io-ts and zod, both provide a safe layer on top of data sources that is great to work with with JS and TS.

I'm not saying "defensive programming" is always bad. My point was mainly that, from my experience at least, more often than not folks default to do an early return when the issue could be solved in a better way.

Thread

Thread

Cheers for those library links! I haven't used them before, but they look really intriguing.

Agree that often you see the "right now" solution instead of the "right" solution (and I mean "'right' solution" very loosely, in that there is no one way to skin a cat).

Sometimes the competing budget, timeline, and plethora of other nagging responsibilities prevents us from taking the time think through these logical issues and refactor with leaner, more readable code, for sure. On the other hand, while some might find an early return more verbose, requiring more concentration to read, I feel they can still be as robust as any other logic that performs a similar function.

How great that all of this good discussion on programming patterns has spiralled out from a post on conditionally rendering JSX! 😄

Thread

Thread

Adding that it depends on the architecture of the frontend in this case.
When I do a "standard React SPA" I barely use this early return, but having Next JS with SSR and a headless CMS as source of structure/data makes it almost mandatory. In this last case there's a builder which receives a big JSON from a micro which defines which components will show up on a given view through a dynamic component loader process.

We cannot ensure the structure and/or content inside the CMS to be reliable as it's human made, and during those months it has been proved that human errors could appear.

I agree on that

Both have the problem that you're optionally calling hooks, so both are going against the rules of hooks.

generally speaking, but not in this context.
As it's Server Side Rendered, it will not magically get the data afterwards through an async process or whatever. The structure and data you receive into the JS is the "director" about what and how will be shown, and what you actually handle in the view is the pre-rendered in which you can safely use this early return to avoid further execution of that piece of software.

you can use a Node API and/or handle requests to third parties or other micros in the server context (getServerSideProps) but you can need, eventually, to add an async call to get data from a micro or third party to search or filter.
Then you have two options, either reload the view adding query params or whatever, handling it in the Node JS context to keep it full-SSRed, or do the async thingy (fetch) in the frontend, in which case I'd recommend a loader/spinner, then the conditional render will look like that:

return loading ? <Loader /> : <DataTable data={fetchedData} />

and you'll never return null nor undefined because there's no sense on it.

Comment button Reply


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK