Skip to main content

JSX Deep Dive: Beyond the Basics (Part 1) #16

πŸ“– Introduction​

Following our exploration of Class Components, this article delves into the intricacies of JSX. While you've already been using JSX to write what looks like HTML in your components, this deep dive will uncover what's happening under the hood. Understanding how JSX is transformed into plain JavaScript is essential for writing more efficient and bug-free React code.


πŸ“š Prerequisites​

Before we begin, please ensure you have a solid grasp of the following concepts:

  • JavaScript Objects and Functions: A clear understanding of how JavaScript objects are created and how functions work.
  • React Components: You should be comfortable creating and rendering basic functional and class components.
  • Basic HTML: Familiarity with standard HTML tags and attributes.

🎯 Article Outline: What You'll Master​

In this article, you will learn:

  • βœ… The "Why" of JSX: Understanding the problem JSX solves and why it's preferred over the alternative.
  • βœ… The Transformation Process: A detailed look at how JSX is compiled into React.createElement() function calls.
  • βœ… From Function to Object: Seeing how React.createElement() returns a JavaScript object that React uses to construct the DOM.
  • βœ… Practical Examples: Converting various JSX snippets to their React.createElement equivalents.
  • βœ… The Role of Babel: A brief introduction to the tool that makes JSX possible.

🧠 Section 1: The Core Concepts of JSX Transformation​

Before writing any code, it's crucial to understand the foundational theory. JSX is syntactic sugar. It provides a concise and familiar syntax for creating React elements, but it's not something browsers understand natively. It's a paradigm for describing your UI within your JavaScript code.

The core problem JSX solves is the verbosity and poor readability of creating complex UI structures with plain JavaScript. Without JSX, you would have to use the React.createElement() function for every single element, which can quickly become a nested nightmare.

Key Principles:

  • Principle 1: JSX is an Abstraction: JSX is a higher-level syntax that abstracts away the underlying React.createElement() calls. It lets you write markup that is more intuitive and closer to the final HTML output.
  • Principle 2: Compilation is Mandatory: Browsers cannot execute JSX directly. A compiler, like Babel, must transpile your JSX code into standard JavaScript that browsers can understand.
  • Principle 3: Everything is an Object: The ultimate output of a JSX expression, after being compiled and executed, is a regular JavaScript object. React reads these objects (often called "React elements") to build the DOM and keep it up to date.

πŸ’» Section 2: Deep Dive - From JSX to JavaScript Object​

Now, let's translate theory into practice. We'll start with the simplest possible JSX and see how it gets transformed.

2.1 - Your First Transformation: A Simple <h1>​

Here is a foundational example of JSX:

// code-block-1.jsx
const element = <h1>Hello, world!</h1>;

This looks simple, but a lot is happening behind the scenes.

Step-by-Step Code Breakdown:

  1. The JSX: We write what looks like a standard HTML <h1> tag and assign it to a constant.
  2. The Compilation (Babel): When you build your React application, a tool called Babel steps in. It finds this JSX syntax and converts it into a React.createElement() function call.

The code above becomes:

// code-block-2.js
const element = React.createElement('h1', null, 'Hello, world!');
  1. The Function Call: This is plain JavaScript. The React.createElement() function is called with three arguments:

    • type: The type of element. This can be a string for an HTML tag ('h1', 'div') or a reference to a React component.
    • [props]: An object containing the properties (attributes) you want the element to have. In this case, there are no props, so it's null.
    • [...children]: The content inside the element. Here, it's the string 'Hello, world!'.
  2. The Final Object: When React.createElement() runs, it returns a simple JavaScript object. This object is a lightweight description of what to render.

If you were to console.log(element), you would see an object similar to this:

{
"type": "h1",
"props": {
"children": "Hello, world!"
},
// ... other internal properties
}

This object is the "React element" that React uses to efficiently update the actual browser DOM.

2.2 - Adding Props and Children​

Let's look at a slightly more complex example with attributes and nested elements.

// code-block-3.jsx
const element = (
<div className="greeting">
<h1>Hello!</h1>
<p>Welcome to our app.</p>
</div>
);

The Transformation:

Babel converts this JSX into a nested React.createElement() structure.

// code-block-4.js
const element = React.createElement(
'div',
{ className: 'greeting' },
React.createElement('h1', null, 'Hello!'),
React.createElement('p', null, 'Welcome to our app.')
);

Walkthrough:

  • The Parent div: The outer createElement call defines the div.
  • The props Object: The second argument is now an object { className: 'greeting' }. This is how JSX attributes are passed.
  • The children: The subsequent arguments are the children of the div. Each child is itself a React.createElement call, creating the nested structure.

This demonstrates why writing JSX is so much cleaner. The nested function calls are powerful but become very difficult to read and maintain manually as the UI grows in complexity.


πŸ› οΈ Section 3: Project-Based Example: Building a User Profile Card​

It's time to apply our knowledge to a practical scenario. We will build a simple user profile card and then look at its compiled React.createElement equivalent to solidify our understanding.

The Goal: Create a simple, static component that displays a user's name, a short bio, and an avatar image.

The Plan:

  1. Write the component using JSX.
  2. Analyze the equivalent React.createElement code.
// project-example.jsx
import React from 'react';

function UserProfile() {
const user = {
name: 'Hedy Lamarr',
imageUrl: 'https://i.imgur.com/yXOvdOSs.jpg',
bio: 'Inventor and actress.'
};

return (
<div className="profile-card">
<img
className="avatar"
src={user.imageUrl}
alt={'Photo of ' + user.name}
style={{
width: 100,
height: 100,
borderRadius: '50%'
}}
/>
<h1>{user.name}</h1>
<p>{user.bio}</p>
</div>
);
}

export default UserProfile;

The React.createElement Equivalent:

While you would never write this by hand, seeing the compiled output is incredibly insightful.

// project-example-compiled.js
import React from 'react';

function UserProfile() {
const user = {
name: 'Hedy Lamarr',
imageUrl: 'https://i.imgur.com/yXOvdOSs.jpg',
bio: 'Inventor and actress.'
};

return React.createElement(
'div',
{ className: 'profile-card' },
React.createElement('img', {
className: 'avatar',
src: user.imageUrl,
alt: 'Photo of ' + user.name,
style: {
width: 100,
height: 100,
borderRadius: '50%'
}
}),
React.createElement('h1', null, user.name),
React.createElement('p', null, user.bio)
);
}

export default UserProfile;

This side-by-side comparison makes the value of JSX crystal clear. It provides a developer-friendly syntax that directly translates to the powerful but verbose underlying API.


πŸš€ Section 4: Advanced Techniques: Components as Types​

So far, we've only used strings ('div', 'h1') for the type argument. But the real power of React comes from composing components. When you use a custom component in your JSX, the type becomes a direct reference to your component function.

Consider this JSX:

// code-block-5.jsx
import UserProfile from './UserProfile';

function App() {
return <UserProfile />;
}

The compiled output is:

// code-block-6.js
import UserProfile from './UserProfile';

function App() {
return React.createElement(UserProfile, null);
}

Notice that UserProfile is passed directly as the type. React sees that the type is a function (or a class), and knows it needs to render that component, rather than a standard HTML tag. This is the fundamental concept that enables the entire component model.


✨ Section 5: Best Practices and Anti-Patterns​

Best Practices:

  • Embrace JSX: Don't try to avoid JSX by writing React.createElement manually. JSX is the idiomatic way to write React applications.
  • Keep it Readable: Use parentheses () to wrap multi-line JSX expressions. This improves readability and can prevent certain JavaScript syntax errors.
  • Use a Linter: Tools like ESLint with the eslint-plugin-react will enforce best practices and catch common errors in your JSX automatically.

Anti-Patterns (What to Avoid):

  • Complex Logic in JSX: Avoid putting very complex logic directly inside your JSX. If you have complicated if/else chains, extract them into variables or helper functions above the return statement.

Don't do this:

// Too much logic in the return statement
return (
<div>
{(() => {
if (conditionA) return <p>A</p>;
if (conditionB) return <p>B</p>;
return <p>C</p>;
})()}
</div>
);

Do this:

// Logic is extracted for clarity
let content;
if (conditionA) {
content = <p>A</p>;
} else if (conditionB) {
content = <p>B</p>;
} else {
content = <p>C</p>;
}

return <div>{content}</div>;

πŸ’‘ Conclusion & Key Takeaways​

Congratulations! You've peeled back the curtain on JSX and now understand that it's not magicβ€”it's a powerful compiler abstraction over plain JavaScript.

Let's summarize the key takeaways:

  • JSX is Syntactic Sugar: It provides a concise, HTML-like syntax for the React.createElement() function.
  • Babel is the Translator: A compiler like Babel is required to transform JSX into browser-readable JavaScript.
  • The Output is an Object: React.createElement() returns a lightweight JavaScript object (a React element) that describes the UI. React uses these objects to construct and update the DOM.
  • Readability Wins: JSX makes component code dramatically easier to read, write, and maintain compared to nested React.createElement calls.

Challenge Yourself: Take the UserProfile component and add a new element, like a <ul> with a few <li> elements for the user's skills. Then, try to write out the corresponding React.createElement calls by hand to test your understanding.


➑️ Next Steps​

You now have a solid mental model of how JSX works under the hood. In the next article, "JSX Deep Dive: Beyond the Basics (Part 2)", we will build directly on these concepts to explore the role of Babel in more detail and how the modern JSX transform has made import React from 'react' optional in many files.

Thank you for your dedication. Stay curious, and happy coding!


glossary​

  • JSX (JavaScript XML): A syntax extension for JavaScript that allows you to write HTML-like markup inside a JavaScript file.
  • Transpilation/Compilation: The process of converting source code written in one language (like JSX) into another language (like standard JavaScript). Babel is a popular transpiler.
  • React.createElement(type, props, ...children): The core function in React that creates a React element (a JavaScript object) describing a component.
  • React Element: A lightweight, plain JavaScript object that describes a DOM node or a component instance. It's the central piece of information React uses to construct the UI.

Further Reading​