13

Why You Can't Return Parallel JSX Elements. The Hack!

 4 years ago
source link: https://scotch.io/tutorials/why-you-cant-return-parallel-jsx-elements-the-hack
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.

Table of Contents

  • A Deeper Dive into Return Statements, JavaScript and JSX
  • A closer look at the JSX transpilation
  • Here’s the Solution to working with Parallel JSX Elements

JavaScript as a language doesn’t support multiple return values. In writing React code, your JSX (JavaScript XML) eventually gets transpiled into vanilla JavaScript. When this happens, parallel JSX elements are transpiled into multiple return values which isn’t a feature supported by the language(JavaScript).

nYVBnyE.png!web

A Deeper Dive into Return Statements, JavaScript and JSX

If you’ve written React for quite sometime, you probably have noticed an error of this sort that occurs whenever you try to return more than one element from a React component i,e

import React from 'react'

const Loading = () => {
  return (
    <p>Another parent element</p>
    <div className="text-center">
      <i className="fas fa-spinner fa-3x"></i>
      <h4 className="mb-3">This may take a few seconds. Please wait!</h4>
      <p>Loading....</p>
    </div>
  )
}
export default Loading

Why does this occur? And what does it really mean?

To find the answers, let’s briefly examine how return statements work in Javascript.

In JavaScript, the return statement is used to terminate a function’s execution and return a single value from the function. This single value could be a string, number, array, object, function etc.

The only condition is that it must evaluate to a value. Otherwise undefined is return from the function.

Here’s a typical example:

Essential Reading : Learn React from Scratch! (2019 Edition)
function sum(a, b) {
  return a + b
}

More information about return statements can be found here on the Mozilla Developer Network.

Notice that a return statement terminates the execution of a function. This makes code as shown below flawed as the latter statement can never be reached once the first return statement is executed.

function sumAndDifference(a, b) {
  return a + b
  return a - b
}

OR

function sumAndDifference(a, b) {
  return a + b, a - b
}

A typical solution to this problem in JavaScript would be to group return statements into collections that can be de-structured at a later point.

function sumAndDifference(a, b) {
  return [a + b, a - b]
}

OR

function sumAndDifference(a, b) {
  return {
    sum: a + b,
    difference: a - b
  }
}

This behaviour is what is common across many modern programming languages. Although, some programming languages like “Go” have ways of implementing multiple return statements, in many cases the idea comes down to grouping the items as one unit that can be de-structured when the returned values are needed.

When React components created using JSX get transpiled into vanilla JavaScript, we basically end up with functions that return some constructed markup to be injected into the DOM. See a typical example below:

React code

import React from 'react'

const Loading = () => {
  return(
    <div className="text-center">
      <i className="fas fa-spinner fa-3x"></i>
      <h4 className="mb-3">This may take a few seconds. Please wait!</h4>
      <p>Loading....</p>
    </div>
  )
}

export default Loading

Transpiled Vanilla JavaScript

"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;

var _react = _interopRequireDefault(require("react"));

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

var Loading = function Loading() {
  return _react.default.createElement("div", {
    className: "text-center"
  }, _react.default.createElement("i", {
    className: "fas fa-spinner fa-3x"
  }), _react.default.createElement("h4", {
    className: "mb-3"
  }, "This may take a few seconds. Please wait!"), _react.default.createElement("p", null, "Loading...."));
};

var _default = Loading;
exports.default = _default;

Notice how the function Loading above returns some markup created from a series of nested elements constructed via React’s createElement method.

A closer look at the JSX transpilation

Let’s now take a closer look at the JSX conversion that took place above:

React.createElement("div", {
  className: "text-center"
}, React.createElement("i", {
  className: "fas fa-spinner fa-3x"
}), React.createElement("h4", {
  className: "mb-3"
}, "This may take a few seconds. Please wait!"), React.createElement("p", null, "Loading...."));

Without all the other things that make up a React component, the JSX earlier written translates into the code above. As the React documentation clearly states :

createElement() creates and returns a new React element of the given type. The type argument can be either a tag name string (such as 'div' or 'span'), a React component type (a class or a function), or a React fragment type.

It accepts parameters as shown below:

React.createElement(
  type,
  [props],
  [...children]
)

At conversion, this method is used to recreate the markup originally written in JSX, and then the markup is returned from the function. Notice that in the example above, there is only one enclosing element returned from the React component’s render function. Thus, the transpiled JSX has only one return value which nests every other element enclosed by the parent element.

What happens when there is more than one enclosing element?When there is more than one enclosing element, the transpiled function tends to have two return values(one for each enclosing element). See example below:

React Code

import React from 'react'

const Loading = () => {
  return(
    <p>Another parent element</p>
    <div className="text-center">
      <i className="fas fa-spinner fa-3x"></i>
      <h4 className="mb-3">This may take a few seconds. Please wait!</h4>
      <p>Loading....</p>
    </div>
  )
}
export default Loading

Transpiled Vanilla JavsScript

"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;

var _react = _interopRequireDefault(require("react"));

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

var Loading = function Loading() {
  return _react.createElement("p", {}, "Another parent element"),
  _react.default.createElement("div", {
    className: "text-center"
  }, _react.default.createElement("i", {
    className: "fas fa-spinner fa-3x"
  }), _react.default.createElement("h4", {
    className: "mb-3"
  }, "This may take a few seconds. Please wait!"), _react.default.createElement("p", null, "Loading...."));
};

var _default = Loading;
exports.default = _default;

Notice that in the transpiled code, the function Loading now has multiple return values in the format return a,b .

This would result in an error as this is unsupported in JavaScript, thus unsupported in JSX(JavaScript XML).

Here’s the Solution to working with Parallel JSX Elements

Recall that with vanilla JS, solving this problem would require that you group the statements to be returned into a collection of some sort so that it is treated as one value.

Wrapping all markup in an extra elementAn ideal way to do this in JSX would be to group the elements, by adding an extra wrapping element that encloses all markup. See example below:

import React from 'react'

const Loading = () => {
  return (
    <div>
      <p>Another parent element</p>
      <div className="text-center">
        <i className="fas fa-spinner fa-3x"></i>
        <h4 className="mb-3">This may take a few seconds. Please wait!</h4>
        <p>Loading....</p>
      </div>
    </div>
  )
}

export default Loading

As much as this solves the problem, in some cases you can’t afford to add an extra node to the DOM as it may mess up your styling or page layout. This leads us to the second solution.

Using React Fragment React Fragment is used to group a list of child elements without adding extra nodes to the DOM. See example below:

import React, { Fragment } from 'react'

const Loading = () => {
  return (
    <Fragment>
      <p>Another parent element</p>
      <div className="text-center">
        <i className="fas fa-spinner fa-3x"></i>
        <h4 className="mb-3">This may take a few seconds. Please wait!</h4>
        <p>Loading....</p>
      </div>
    </Fragment>
  )
}

export default Loading

This creates a container element such that when the code is transpiled it is wrapped within a React fragment which does not add an extra node on injection into the DOM. It has a shorthand syntax which makes use of empty tags as shown below:

import React from 'react'

const Loading = () => {
  return (
    <>
      <p>Another parent element</p>
      <div className="text-center">
        <i className="fas fa-spinner fa-3x"></i>
        <h4 className="mb-3">This may take a few seconds. Please wait!</h4>
        <p>Loading....</p>
      </div>
    </>
  )
}

export default Loading

Here’s what we learned in this article:

createElement()

Like this article? Follow @worldclassdev on Twitter


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK