React First Program

Chapter 5 31 mins

Learning outcomes:

  1. Writing your first React program
  2. Rewriting the same program using JSX
  3. The declarative beauty of React
  4. No JSX means no Babel

Introduction

In the previous chapters, React Quick Setup and React Manual Setup, we set up the entire environment for working with React code.

Beyond this point, it's assumed that you have the same local setup configured on your end. We won't be using the external <script>s approach of working with React. (If however, you wish to go that way, you'll have to change certain things throughout this course.)

Now in this chapter, we'll be writing our very first React program.

Once we write the code in the index.js file in the src directory, we'll need to enter npm start in the terminal in order to launch the React application in the browser.

We'll start by creating a program without using JSX to truly appreciate what happens behind the scenes in a React application, and then move over to creating the same program using JSX. In this way, we'll truly understand how JSX gets transpiled down to normal JavaScript. And amid all of this, we'll get to know of a plethora of other handy ideas.

So, let's get going.

The program (without JSX)

Recall the index.js file that we created inside the src directory (inside react-course) in the previous chapters. It is where we'll be writing our very first React program.

The first thing that we need to do in index.js is to import the react package. As stated before, react defines the core functionality of React, which is used in every single React application.

So let's do this:

index.js
import React from 'react';

Notice the name of the default import here — it's called React, in Pascal casing, which might hint to use that React is a constructor function. In reality, though, this is not the case.

The default export of the react package is a pure object (an instance of Object), with methods defined on it to provide different utilities to users of the package.

Why is the default import of react named as React?

Naming the default import of react as React is purely a convention set by React, and honestly speaking, quite a good convention. It helps us distinguish mere objects created in our programs from such large containers of utilities.

We'll see a similar situation when we import the react-dom package in the following discussion.

With the import done, next we need to create a new React element. This is with the help of the createElement() method of React (the object that we just imported).

The syntax of createElement() is fairly straightforward:

createElement(tagName, props, ...children)

The first tagName argument is the name of the element that we want to create (or a reference to a component, more on that later). Examples are 'h1', 'p', 'strong', etc.

The second props argument is a simple object containing all the properties that we want on the element. The names of these properties is almost the same as the names of properties of element nodes in the HTML DOM.

For example, the HTML class attribute is denoted as className in HTML DOM, and similarly as className in React.

The third argument, and every subsequent argument from that point onwards, specifies the child node to put into the React element being created. Possible types of values are strings and React elements (more on that later).

For example, if we want to put the text content 'Hello World!' inside the element, we'll pass the value 'Hello World!' as the third argument.

In our case, we want to create an <h1> element similar to the following, inside the root element that we saw in the previous chapters:

<h1 contenteditable="true">Hello World!</h1>

Let's do so.

Below we create this <h1> in React using createElement():

index.js
import React from 'react';

const element = React.createElement(
   'h1',
   { contentEditable: true },
   'Hello World!'
);
  • The first argument is 'h1' since we need an <h1> element.
  • The second argument is an object defining contentEditable to be true, as we want the contenteditable HTML attribute on the <h1> element. The name of the property comes directly from how it's named in the HTML DOM.
  • The third argument is 'Hello World!', exactly matching the text that we want to put inside the element.

The value returned by a call to createElement() is a React element.

At this point, the <h1> element has been created in React and stored in the element constant, but it's still not inserted into the HTML document.

To do so, we need the renderer react-dom package, and particularly it's client subpackage.

Let's go ahead and import react-dom:

index.js
import React from 'react';
import ReactDOM from 'react-dom/client'; const element = React.createElement( 'h1', { contentEditable: true }, 'Hello World!' );

As before, notice the naming of the default import of react-dom/client — it's named ReactDOM which is again in Pascal case, keeping with the convention of React of naming default imports in Pascal case.

react-dom vs. react-dom/client

If you've worked with react-dom before as well, you might've noticed a difference here. That is, we've imported react-dom/client instead of importing react-dom.

Before version 16.8, react-dom was only limited to the client, i.e. the front end. And so, the react-dom package itself provided all the utilities for rendering React for the HTML DOM.

But then later on, react-dom got broken down into two parts, one for the client (in the client module) and one for the server (in the server module).

To use react-dom on the client, we need to import react-dom/client — the client module from the react-dom package.

Up next, we need to define the #root element as React's root. This is done using the createRoot() method of ReactDOM (the object that we just imported).

The createRoot() method accepts the element node that we want as React's root node, and then returns back an object which can be used, thereafter, for rendering React elements.

The code below accomplishes this:

index.js
import React from 'react';
import ReactDOM from 'react-dom/client';

const element = React.createElement(
   'h1',
   { contentEditable: true },
   'Hello World!'
);

const root = ReactDOM.createRoot(document.querySelector('#root'));

With the root defined, next we need to render the <h1> React element we created a while ago. To do so, we require the render() method of the object returned by createRoot() (stored in root in our code).

The render() method accepts a React node and then renders it inside the calling root object (which is returned by a call to ReactDOM.createRoot()).

But wait. What exactly is a React node?

Well, a React node is anything that we could pass to the render() method shown above or to createElement(), starting from its third argument. A React element is a React node; a mere string is a React node; even null is a React node.

In the code below, we render our React element, element, into the root:

index.js
import React from 'react';
import ReactDOM from 'react-dom/client';

const element = React.createElement(
   'h1',
   { contentEditable: true },
   'Hello World!'
);

const root = ReactDOM.createRoot(document.querySelector('#root'));
root.render(element);

And we're done writing our code. It's now time to get this working.

Go ahead and open up the react-course directory in the terminal and run the npm start command:

.../react-course> npm start

If you're using the manual setup that we showed in the previous chapter, you'll need to additionally invoke npm run build to ignite Rollup. Otherwise, you don't need this command:

.../react-course> npm run build

And we are done. Yes, that simple!

Open up the browser and navigate to localhost:3000. The webpage should look like this:

Hello World!

If you get the same output, great!

You just completed writing your very first React program.

Now, let's rewrite this same program using JSX.

The program (with JSX)

There's one and only one way to create elements in React and that is by using createElement(), as we did above. JSX, as we shall see below shortly, is merely syntactic sugar over these createElement() calls.

JSX gives us an HTML-like syntax to create React elements. The result is that it's superbly easy to construct intricately nested trees of elements and modify their attributes right while we are working in JavaScript, as we're all intrinsically used to HTML and its rudimentary syntax.

Thereafter, this JSX is converted back to normal JavaScript using some kind of a tool — often referred to as a transpiler — which is almost always Babel.

So to rewrite the same code above using JSX, we need to replace the calls to createElement() with simple JSX code.

Let's do this now.

First, let's quickly review the last code snippet from the section above:

index.js
import React from 'react';
import ReactDOM from 'react-dom/client';

const element = React.createElement(
   'h1',
   { contentEditable: true },
   'Hello World!'
);

const root = ReactDOM.createRoot(document.querySelector('#root'));
root.render(element);

We'll remove the React.createElement() expression in line 4 and instead use the corresponding JSX in place of it. And just to make this example a little different from the previous one, we'll also add some text after the greeting.

Here's the new code:

index.js
import React from 'react';
import ReactDOM from 'react-dom/client';

const element = <h1 contentEditable={true}>Hello World! (From JSX)</h1>; const root = ReactDOM.createRoot(document.querySelector('#root')); root.render(element);

Right as soon as you see this code, you say that it's invalid JavaScript. Indeed it is invalid, and that's why we installed Babel in the first place — to help convert this alien syntax to one that a web browser can easily understand, i.e. normal JavaScript.

Don't worry if you can't understand the JSX code presented here right now; we'll learn a lot more about JSX in the upcoming chapters.

Let's now go ahead and enter the same commands in the terminal that we entered for our previous program.

Once everything is done, open up http://localhost:3000 in the browser. Here's what we get:

Hello World! (From JSX)

Voila! Our JSX is running absolutely flawlessly.

Before we move on, it's worthwhile pointing out that whenever we are assigning JSX to a variable, or maybe returning it from a function, it's better to encapsulate it in a pair of parentheses (()) to make the code more readable.

Recall that the parentheses (()) can be used around any expression in JavaScript to group that expression, for e.g. (2 + 3) * 5.

Therefore, the code above should be written as follows:

index.js
import React from 'react';
import ReactDOM from 'react-dom/client';

const element = (
<h1 contentEditable={true}>Hello World! (From JSX)</h1>
); const root = ReactDOM.createRoot(document.querySelector('#root')); root.render(element);

The pair of parentheses helps us clearly distinguish bits of JSX elements in our code.

The declarative beauty of React

Before we end this chapter, it's worthwhile to discuss about the beauty of React in how it simplifies even the addition of one element to the DOM, as compared to vanilla JavaScript.

Let's contrast the imperative nature of the DOM with the declarative nature of React.

Suppose we want to create the same <h1> element that we created above and place it inside the #root element but this time without React.

Obviously, one option is to use innerHTML as shown below:

const rootElement = document.querySelector('#root');
rootElement.innerHTML = '<h1>Hello World!</h1>';

This does work.

However, we don't want just about anything that 'works'. Rather, we want a way in which we could easily configure the element node as we wish to — for example, assign event handlers to it from within JavaScript, not from the HTML.

Let's say we want to register a click event handler on the <h1> element that logs the text 'Clicked'.

Surely, this is possible with the onclick HTML attribute on the element, as shown below:

const rootElement = document.querySelector('#root');
rootElement.innerHTML = '<h1 onclick="console.log(\'Clicked\')">Hello World!</h1>';

But this is not what we want. The onclick attribute is unnecessarily crowding up the HTML — a practice that isn't recommended these days at all — and not just this, but it's also making the string (assigned to innerHTML) error-prone to type with the \' escape sequences.

The approach that we're talking about is to manually create an element node using document.createElement(). When we create an element in this way, we have complete control over it.

We can register event listeners on it, customize those listeners, add the element inside other element nodes, add given nodes inside the element itself, and much much more. If we want to, we can even store some data on the element node as properties of the node object.

Let's create the <h1> element with this approach, along with the click event handler:

const rootElement = document.querySelector('#root');

const h1Element = document.createElement('h1');
h1Element.textContent = 'Hello World!';
h1Element.onclick = function(e) {
   console.log('Clicked');
};

rootElement.appendChild(h1Element);

This code is imperative. By 'imperative', we mean that it shows exactly how to add a given node to the DOM. We don't just specify that we want an <h1> element containing the text 'Hello.' No. Instead, we do all of this manually.

Imperative programming is when we see 'how' something is done.

Let's contrast this with the React code accomplishing the same thing (ignoring the imports as they're not needed in the comparison):

index.js
const element = React.createElement(
   'h1',
   {onClick: function(e) { console.log('Clicked'); }},
   'Hello World!'
);

const root = ReactDOM.createRoot(document.querySelector('#root'));
root.render(element);

This code is declarative. By 'declarative', we mean that it shows what we're trying to achieve, i.e. to create an <h1> element and then put it inside #root, not how to achieve it.

Notice the naming of the onClick property in the code above; it's NOT onclick with all lowercase letters as is otherwise the case in HTML. This is a consequence of the way React treats events. We'll learn more about events and this different naming in React Events.

Declarative programming is when we see 'what' is done, with the 'how' being abstracted away from us.

Indeed, React.createElement() fosters declarative programming, but JSX takes it to a whole new level, as follows:

index.js
const element = (
   <h1 onClick={function(e) {console.log('Clicked')}}>Hello World!</h1>
);

const root = ReactDOM.createRoot(document.querySelector('#root'));
root.render(element);

In JSX, we don't even need to worry about calling a method, setting its first argument to 'h1', its next argument to an object, and so on — we just 'declare' what we want, rightaway; React and Babel neatly then handle the rest.

React is declarative in nature and, thus, abstracts away a heck lot of complexities from us when working with the HTML DOM.

As you'll encounter more and more React code later on in the coming chapters, you'll see that building UIs in this declarative style is exceptionally intuitive. It makes the code a whole lot more readable, compact, maintainable, and scalable.

(opt) No JSX means no Babel

It's extremely important to realize the fact that if we don't use JSX in our React programs, which is almost never going to be the case, we don't need Babel.

Babel is exclusively used as a transpiler to convert JSX code, or latest ECMAScript code, into normal JavaScript code that every other browser can easily understand.

But how can we confirm this?

Well, there are two ways to confirm this. In the previous two chapters, we saw three ways of setting up a React development environment: using external <script>s on an HTML page; using CRA; and finally using Rollup.

To confirm the omission of JSX allowing an omission of Babel, we can employ either the first approach or the third one. Demonstrating this using CRA is possible but it would require a lot of work and so we prefer avoiding all that right now.

Demonstrating using external <script>s

So, let's begin with demonstrating this idea for the first approach.

Consider the following code:

<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="utf-8">
   <title>Learning React</title>

   <script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
   <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
   <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
   <div id="root"></div>

   <script type="text/babel">
      // React code goes here.
   </script>
</body>
</html>

Notice the third <script> that calls on to Babel, and also notice the <script> inside <body> that is meant to contain our React code — it has the type attribute set on it in order to prevent the browser from directly executing it and instead get Babel to do the honors.

Now, we shall remove the Babel <script> from the <head> and also remove the type attribute from the last <script> since we'll be writing normal React without using JSX:

<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="utf-8">
   <title>Learning React</title>

   <script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
   <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
</head>
<body>
   <div id="root"></div>

   <script>
      // React code goes here.
   </script>
</body>
</html>

Perfect. Let's now write our React code.

We'll be rendering an <h1> element inside the #root element.

Here's the code:

<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="utf-8">
   <title>Learning React</title>

   <script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
   <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
</head>
<body>
   <div id="root"></div>

   <script>
      const element = React.createElement('h1', null, 'Hello World!');
      const root = ReactDOM.createRoot(document.querySelector('#root'));
      root.render(element);
   </script>
</body>
</html>

Let's try running this and see whether we get anything displayed on the webpage:

Live Example

As per our expectations, the code indeed works, even though we've thrown away the Babel library. Why, you ask? Well, because we didn't use JSX. If we did use it, our code would've thrown an error.

This is demonstrated as follows, showing only the contents of the last <script>:

const element = (
   <h1>Hello World!</h1>
);
const root = ReactDOM.createRoot(document.querySelector('#root'));
root.render(element);

Live Example

Here's what the console shows:

Uncaught SyntaxError: Unexpected token '<'

The browser is unable to parse the <h1> tag in line 2. This shouldn't be any surprising to us since JSX indeed is invalid JavaScript in many instances.

If at this stage, we bring back our Babel library and the type="text/babel" attribute on the last <script>, we should be able to get this JSX code be executed. But once again, remember that the browser won't be executing this code; instead, Babel would be doing so.

Here's our new code:

<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="utf-8">
   <title>Learning React</title>

   <script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
   <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> </head> <body> <div id="root"></div>
<script type="text/babel"> const element = ( <h1>Hello World!</h1> ); const root = ReactDOM.createRoot(document.querySelector('#root')); root.render(element); </script> </body> </html>

Let's see what we get now:

Live Example

And the code works! And why wouldn't it, for it is valid JSX at the end of the day.

Demonstrating using Rollup

Now, let's demonstrate this same idea using Rollup.

In our Rollup configuration file, recall that we added a plugin for integrating Babel into the bundling process, @rollup/plugin-babel. Now, we shall comment out the code in the configuration file integrating this plugin and then try running our normal React code without JSX.

First, we'll open up the rollup.config.mjs file and comment out the code making the call to the @rollup/plugin-babel plugin:

import nodeResolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import babel from '@rollup/plugin-babel';
import replace from '@rollup/plugin-replace';

export default {
    input: 'src/index.js',
    output: {
        file: 'public/bundle.js',
        format: 'iife'
    },
    plugins: [
        nodeResolve({
            extensions: ['.js', '.jsx']
        }),
        /* babel({
            babelHelpers: 'bundled',
            presets: ['@babel/preset-react'],
            extensions: ['.js', '.jsx']
        }), */
        commonjs(),
        replace({
            preventAssignment: false,
            'process.env.NODE_ENV': '"development"'
        })
    ]
}

Here's our React code in index.js:

const element = React.createElement('h1', null, 'Hello World!');
const root = ReactDOM.createRoot(document.querySelector('#root'));
root.render(element);

As you can see, there is absolutely no JSX in there.

Let's now run this code and see whether it completes without any errors.

Go on and enter npm run build (to ignite Rollup) followed by npm start (to spin up a web server). Finally, open up the browser and navigate to http://localhost:3000.

What do you see? Well, we indeed see the webpage with our <h1> element rendered.

Despite the fact that the Babel plugin was removed from the Rollup setup, it didn't lead to any kind of errors in our bundling. The reason is simple: there's no need of Babel in the given code.

But there sure will be a need of Babel as soon as we transition to JSX. Shall we transition to JSX? Let's see what we get.

First off, replace React.createElement() in the code with the corresponding JSX:

index.js
import React from 'react';
import ReactDOM from 'react-dom/client';

const element = (
   <h1 contentEditable={true}>Hello World! (From JSX)</h1>
);

const root = ReactDOM.createRoot(document.querySelector('#root'));
root.render(element);

Also make sure that the babel plugin in rollup.config.mjs is still commented out.

As before, run the commands npm run build and then npm start to bundle and serve the React program, respectively, followed by opening up the HTML document in the browser.

Did you notice any errors this time in the bundling? Well, we did.

Well, we didn't have to go as far as npm start; npm run build ended up with an error to begin with. And that's by virtue of the fact that JSX was used, however Babel wasn't there to parse (and convert) it.

Here's part of the error message generated by Rollup:

src/index.js → public/bundle.js... [!] (plugin commonjs--resolver) SyntaxError: Unexpected token (4:16) in ...\Desktop\react-course\src\index.js src/index.js (4:16) 2: import ReactDOM from 'react-dom/client'; 3: 4: const element = <h1>Hello World! (From JSX)</h1> ^ 5: 6: const root = ReactDOM.createRoot(document.querySelector('#root')); at Parser...

The error message is clearly not easy to parse, thanks to Node.js, but we do seem to find a SyntaxError word, meaning that the code was syntactically invalid. And it should indeed be so — we are expecting JSX to be parsed by the bundler as normal JavaScript, yet it ain't a part of JavaScript's grammar at all!

Now uncomment the commented babel plugin code in rollup.config.mjs and repeat the bundling and serving commands.

This time, everything should work absolutely smoothly since we have Babel to take care of our JSX.

The rule of thumb is simple:

To use JSX, we need Babel so that it could transform the JSX code to understandable createElement() calls that all JavaScript engines can make sense of.