React: Foundation — First Program

React First Program

Learning outcomes:

  • Writing your first React program
  • Rewriting the same program using JSX
  • The declarative beauty of React
  • No JSX means no Babel

Introduction

At this point, we have the entire environment set up for creating a React application, either by using a manual workflow or the workflow put forth by the Create React App tool.

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

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. 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 core 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:

javascript
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 us 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.

A React element is basically just an abstraction over an HTML DOM element, or a concrete instance of a component (we shall learn more about components later on).

To create a React element, we ought to use the createElement() method of React (the object that we just imported).

The syntax of createElement() is fairly straightforward:

createElement(type, props, ...children)

The first type argument specifies the type of the element we wish to create. We can either create an element representing an HTML DOM element or one representing an instance of a component. When, it's the former, type is simply a string specifying the tag name of the element. for e.g. 'h1', 'p', 'strong', etc.

The second props argument is a simple object containing all the properties that we want on the element. In React speak, we refer to these properties as props, hence the name of the parameter.

In the case of HTML DOM elements, we can use these props to conveniently set attributes on the DOM elements. For example, the HTML class attribute is denoted as className in HTML DOM, and similarly as the className prop 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, for mere text nodes, and React elements (the same one we're creating now).

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.

The value returned by a call to createElement() is a React element. This can be passed on to another createElement() call, to be put inside the element being created by that call, or directly rendered on to the document using the react-dom package (more on that below).

Let's consider a simple example.

We want to create an <h1> element similar to the following,

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

and put it inside the #root element in the HTML document (index.html in the public directory) of our React app.

Let's do so.

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

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

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 package react-dom, and particularly it's client subpackage (referred to as react-dom/client).

react-dom integrates with the react package in order to render the UI — that is, whatever is built using createElement() calls — to the HTML document.

There are other renderers in React as well, for e.g. to render the UI into a PDF, or into a stream of text to be sent as part of an HTTP response.

Let's go ahead and import react-dom/client:

javascript
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 React's convention 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 React 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).

Consequently, 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 in our HTML document as React's root, that is, the element where the entire UI is rendered by React. 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 the UI into it.

The code below accomplishes this:

javascript
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 takes in a React node and renders it inside the calling root object (the root constant in our case).

But wait. What exactly is a React node?

Well, a React node is anything that we could pass to this render() method or to createElement(), starting from its third argument.

A React element is a React node; a mere string is a React node; a number is a React node; even null is a React node; and so on.

In the code below, we render our <h1> element, element, onto the document by calling the render() method on root and providing it element as an argument:

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

For example, if we need to create a <div> element using JSX, we'll write this:

JSX
<div>This is JSX</div>

That looks HTML, doesn't it? Well, it's not; it's JSX.

The result is that it's superbly easy to construct complex UIs, with intricately nested trees of elements, right from within JavaScript — after all, we developers are all quite comfortable working with HTML and its rudimentary syntax, right?

Now JSX, precisely speaking, is invalid JavaScript as far as the browser is concerned. It needs to be converted back to normal JavaScript using some kind of a tool — often referred to as a transpiler. The de-facto JSX transpiler is Babel.

Let's now recreate the <h1> element above using JSX.

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

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

From here, 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 text 'Hello World!'.

Here's the new code:

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

And that's it! We're done transitioning our React app to use JSX.

The code to create the <h1> goes from this:

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

to this:

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

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

As you read the JSX code, you'll notice that it's invalid code. Indeed, as we stated earlier, JSX is invalid JavaScript, 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, that is, plain JavaScript.

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

Once everything is done, open up 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 working in JSX, it's better to encapsulate a JSX element in a pair of parentheses (()) to make the code more readable — this alien syntax doesn't really stand out without the use of () in JavaScript.

So, the code above should be written as follows:

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

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

We'll revisit JSX in great depths in the chapter React JSX.

The declarative beauty of React

Before we end this chapter, it's worthwhile discussing 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:

javascript
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 HTML; change its styles dynamically; set/remove attributes from it; and so on.

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:

javascript
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:

javascript
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):

javascript
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 NOTonclick 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:

JSX
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 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 a subsequent 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:

HTML
<!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 plain JavaScript (without any JSX):

HTML
<!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 JavaScript code.

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

Here's the code:

HTML
<!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>:

JSX
// We're using JSX, likewise this won't work!
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 3. This shouldn't be any surprising to us since JSX indeed is invalid JavaScript.

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:

HTML
<!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 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:

javascript
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:

JavaScript
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 here.

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 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 do this transition? Let's do it and see what we get.

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

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 localhost:3000 in the browser.

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

We didn't have to go as far as npm start or opening up localhost:3000; 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 ...\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 also a caret pointing towards the <h1> tag in our code.

Indeed, we must get an error — we are expecting JSX to be parsed by the bundler as plain 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:

We need Babel when, and only when, we use JSX in React so that it could transform the JSX code into plain JavaScript that all browsers can make sense of.

To review the actual, technical aspect of the transpilation, each JSX element gets converted into a createElement() plain JavaScript function invocation that any browser can execute.

Spread the word

Think that the content was awesome? Share it with your friends!

Join the community

Can't understand something related to the content? Get help from the community.

Open Discord

 Go to home Explore more courses