Introduction

In the last chapter, React Setup, we setup the entire environment for working with React code, with the help of the module bundler program, Rollup. Beyond this point, it's assumed that you have the same setup configured on your end.

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

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. We'll understand how JSX gets transpiled down to normal JavaScript. In between 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 chapter, React Setup. It is where we'll be writing our very first React program.

But before writing anything in index.js, we'll first need to add some content to our index.html file (in the public directory).

Right now, it looks something as follows:

<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <title>Learning React</title>
</head>
<body>
</body>
</html>

Go on and create a #root element inside <body>, as shown below:

<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <title>Learning React</title>
</head>
<body>
<div id="root"></div> </body> </html>

This is where React will place its content, as we shall see later on.

Perfect! Let's move on.

The next thing that we need to do is to import the react and react-dom packages in our index.js file. As stated before, react defines the core functionality of React while react-dom is React's renderer for the web (apparent by the term 'dom' in it).

So let's do this:

index.js
import React from 'react';

You might've noticed in both the import statements that we used a default import and Pascal casing to name the imported object.

Why is this so? Well, it's purely a convention, and a fairly standard one. Remember that following common conventions in programming is really helpful.

With these imports 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:

React.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 literal object containing all the properties that we want on the element. The names of these properties is almost the same as the names of element node properties in 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. 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 achieve the following outcome for the #root element:

<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <title>Learning React</title>
</head>
<body>
   <div id="root">
<h1 contenteditable="true">Hello World!</h1> </div> </body> </html>

Likewise, let's go on and get React to add content to the #root element to match this given source.

First we ought to create a React element using React.createElement():

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

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 third argument is 'Hello World!', exactly matching the text that we want to put inside the element.

At this point, the element has been created and stored in the element constant, but it's still not inserted into the HTML DOM tree.

To do so, we need the renderer react-dom package.

Before version 16.8, react-dom was only limited to the client, i.e. the front end. But then later, it was broken down into two parts — one for the client and one for the server. To use react-dom on the client, we need to import react-dom/client — the client module from the react-dom package.

Let's go ahead and do this:

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

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 to act 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 React element we just created above. To do so, we require the render() method of the object returned by createRoot().

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()). A React element is a React node; a mere string is a React node; even null is a React node; and so on and so forth.

In the code below, we render a React element, element, into 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 npx rollup command with the -c flag. -c instructs Rollup to use the configuration file for the current directory.

.../react-course> npx rollup -c

This launches the bundling process. It'll take a couple of seconds after which you should get something similar to the following:

.../react-course> npx rollup -c
src/index.js → public/bundle.js...
created public/bundle.js in 8.6s

At this stage, if we inspect the public directory, we see a new bundle.js file. This is what Rollup created in its bundling process.

Now, we're just one step behind of seeing our React app running in the browser, and that is to launch a server serving our react-course/public directory.

Go ahead and type the following command to launch the serve package, serving the public directory on localhost over HTTP:

.../react-course> npx serve ./public

And we are done. 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 way to create elements in React and that is by using React.createElement(), as we did above. JSX, as we shall see below shortly, is merely syntactic sugar over these React.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 syntax. Thereafter, this JSX is converted back to normal JavaScript using some kind of a tool, which is, more often than not, Babel.

So to write 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.

Let's now go ahead and enter the same two npx rollup -c and npx serve ./public commands in the terminal (assuming that the current working directory is react-course) that we used before.

Once the bundling and the server's setup is done, open up the index.html file (in the public directory) in the browser via navigating to localhost:3000/. It should display the following:

Hello World! (From JSX)

Voila! Our JSX is running absolutely flawlessly.

And with this, it's time to understand more details of both these programs.

No JSX means no Babel

In our Rollup configuration file, recall that we added a plugin for integrating Babel into the bundling process. Technically though, we didn't need this plugin in the first program that we create above — the one without any JSX.

This is because the program was already written using valid JavaScript. It's only when we actually write JSX that we need to bring Babel into the equation.

Let's confirm this for real.

Go on and edit the rollup.config.mjs file, you created in the previous chapter, commenting out the line 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']
        }), */
        commonjs(),
        replace({
            preventAssignment: false,
            'process.env.NODE_ENV': '"development"'
        })
    ]
}

Now, rerun the npx rollup command followed by npx serve to bundle and serve the React program. As expected, the whole process should complete without any errors.

Finally, open up the index.html file.

See? We removed the Babel plugin from the Rollup setup, yet it didn't raised any kind of error. The reason is simple: there's no need of Babel here.

But there sure is a need of Babel as soon as write JSX.

Once again, let's replace React.createElement() in line 4 with the corresponding JSX while still keeping the babel plugin in rollup.config.mjs commented out:

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

As before, run the two commands npx rollup and then npx serve to bundle and serve the React program, respectively, followed by opening up the HTML page in the browser.

Did you notice any errors? Well, we did. And that's by virtue of the fact that JSX was used, however Babel wasn't.

Here's part of the error message:

src/index.js → public/bundle.js... [!] (plugin commonjs--resolver) SyntaxError: Unexpected token (4:16) in ...\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!

The rule of thumb is simple:

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

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>';

However, it's 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 we're actually 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:

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.createElement() code we wrote above, 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.

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 a whole new level.

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

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 and, henceforth, more maintainable and scalable.