Topic 6: Introduction to React

What is React?

React (see reactjs.org and react.dev) is a JavaScript user interface library, originally developed by Facebook, which allows complex JavaScript user interfaces to be written in modular, high-level, easy-to-read code. React revolves around the concept of a component. A component is a subsection of a web page, consisting of multiple related HTML elements (e.g. <div>s, form fields and buttons), which all work together to perform a particular item.

For example, we could have a component representing a clock, which consists of a <div> containing the current time. Or we could have a component for an AJAX search, consisting of the text field to enter the search term, the search button and the <div> to display the search results.

React is so-called because it allows us to develop reactive UIs, which automatically update when the application's data changes without us having to implement logic to re-build the UI when that data changes. It does this through the use of state, which will be described below.

Exploring React - Hello World

Here is the React Hello World application:

import React from 'react';
import ReactDOM from 'react-dom/client';

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

If you look at this code, you should notice that the render() method, on the last line, takes a string of HTML which is not in quotes. What is this? It's JSX.

JSX

With React, we use an extension of JavaScript called JSX (see the React documentation). JSX allows us to embed HTML elements within JavaScript code which can simplify coding a component if it contains large amounts of static HTML and only a small amount of variable data. You will see examples of JSX below. Because JSX is not native JavaScript, it needs to be converted to standard JavaScript using Babel - see below.

Explanation of the code

So what's the code example above doing?

Building your first React app

Formerly, the recommended way of creating a React app was with create-react-app, which automatically downloads dependencies and writes build scripts for you. However create-react-app is no longer maintained and therefore its use is not recommended. It still works, but it may break in the future.

Creating a Hello World React app from scratch

Instead, you should create and build it from scratch, making use of npm to install dependencies, Webpack to build, and a third tool, Babel, which we will look at below.

The Hello World React app configuration is available on GitHub at https://github.com/nwcourses/react-starter. We will go through each of its components.

Babel - transpiling JavaScript

Babel is a transpiler: a piece of software which converts between different JavaScript versions. Why is this useful? First of all it allows the newest versions of JavaScript, which may not be supported by older browsers, to be transpiled into older versions which browsers do support. Formerly, Babel was used extensively to transpile ECMAScript 6 upwards into the older version of JavaScript, ECMAScript 5, when the former was not widely supported. Now it is less used for this purpose as ECMAScript 6 and higher are widely supported by browsers.

In the context of React, Babel is important. JSX is not natively supported by browsers so we need to transpile it using Babel into standard JavaScript.

The package.json

We will start our examination of the starter repository with the package.json file, used by npm to download dependencies and build the app.

{
  "name": "react-starter",
  "version": "0.0.1",
  "scripts": {
    "build": "npx webpack"
  },
  "devDependencies": {
    "@babel/core": "^7.23.0",
    "@babel/preset-env": "^7.22.20",
    "@babel/preset-react": "^7.22.15",
    "webpack": "^5.88.2",
    "webpack-cli": "^5.1.4",
    "babel-loader": "^9.1.3"
  },
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  }
}

Hopefully some of this will be familiar to you from last week:

Finally the dependencies include React itself, and react-dom which allows DOM manipulation with React.

The webpack.config.js

Next we will discuss the Webpack configuration file, webpack.config.js, which we also saw last week. Here is the webpack.config.js for this project:

const path = require('path');

module.exports = {
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'public/dist'),
        filename: 'bundle.js'
    },
    optimization: {
        minimize: false
    },
    module: {
        rules: [
            {
                test: /\.(js|jsx)$/,
                exclude: /node_modules/,
                use: {
                    loader: "babel-loader"
                }
            }
        ]
    }
}

We went through most of this last week. However note the rules section within module. We looked at this last week in the context of including CSS files and images with Webpack, and specifying custom loaders to handle these and include them in the bundle. Hopefully you can see that this is testing for files with extension .js or .jsx, and if they occur, handle them with the babel-loader (i.e. they will be transpiled with Babel). We also exclude the node_modules folder as we do not want to wastefully run Babel on any third-party libraries we are using, in which the JavaScript will already be suitable for bundling without the use of Babel.

Note how the index.js is now placed within a src folder. This is a convention commonly used in Webpack-built JavaScript applications. The raw source code does not need to be delivered to the client (the browser), only the built bundle. So there is no need to place the source code inside public, whose role should be primarily to contain publicly-accessible code.

The .babelrc file

Finally we have a third configuration file: .babelrc. This is the Babel configuration file.

{
    "presets": ["@babel/env", "@babel/react"]
}

This tells Babel which transformations to enable. The env transformation is the standard newer-to-older JavaScript transformation, while the react transformation allows transformation of React (JSX) code into standard JavaScript.

Running Webpack in watch mode

One thing we didn't cover last time when looking at Webpack is how to automatically regenerate the bundle if any of the client-side code changes. You can do this by running Webpack in watch mode, e.g.:

npx webpack --watch
See the documentation. This is similar, in some ways, to PM2 in watch mode. However if you run Webpack in watch mode from the command prompt, you will be unable to enter any other commands as Webpack will be continuously running. So if you want to run any other commands you will need to open a separate command prompt.

React Components

As we've already briefly discussed above, React applications consist of a series of reusable components. A component is a user-interface element focused on one particular item, which can be reused in other applications. So we could for example have a clock component, or a login component, or a component which searches for music (via AJAX) and displays the results within the component.

A simple component

React components are written using functions which return JSX. Here is an example:

import React from "react";

function Greeting() {
    return <h1>Hello from the Greeting component!</h1>;
}

export default Greeting;
Note how we write a function called Greeting() which returns JSX to display a paragraph containing the text "Hello!". Ideally, each React component should go in its own file (so this could be saved in greeting.js) and then imported into our application. To better organise our code, we can store it in a folder called components within our src folder. This allows us to reuse the component across multiple applications. Therefore, we are exporting the function so it can then be imported into other code.

Using the component in our app

To use a component, we incorporate it into JSX by writing a tag corresponding to the functio name. So we could incorporate the above component into JSX with the tag <Greeting>. Here is an example of an index.js making use of the component:

import React from "react";
import ReactDOM from "react-dom/client";
import Greeting from "./components/greeting.js";

const root = ReactDOM.createRoot(
    document.getElementById("root")
);

root.render(<Greeting />);
Note how we import the component from the appropriate module (greeting.js within the components folder) and pass in the <Greeting> tag into the render() function.

Props

In most cases, we need to customise the component. For example, we might want a greeting component to greet the user by name. How can we pass the name into the component? The answer is to use props - or properties. Props are specified in the same way as HTML attributes using key/value pairs in the tag, e.g.:

root.render(<Greeting person="John" />);
Here, the <Greeting> component is being passed a person prop with a value of "John".

Here is how we could use the prop in the component:

function Greeting(props) {
    return <p>Hello {props.person}!</p>;
}

export default Greeting;
Note how we specify the props as a parameter to the function. This is an object containing all the props that were passed in; we access the individual prop using its name i.e. props.person and then include it in our JSX. Note how we can embed JavaScript expressions within JSX by surrounding them with curly brackets { and }. Unlike with backtick syntax, we do not need a dollar sign outside the curly brackets.

Here is another example with two props:

function Greeting(props) {
    return <p>Hello {props.firstname} {props.lastname}!</p>;
}

export default Greeting;
which would be rendered, for example, as follows:
root.render(<Greeting firstname="John" lastname="Brown" />);

Object destructuring

We can use an alternative syntax for props as parameters using object destructuring This is a general JavaScript (not React specific) feature which allows us to allocate properties of a JavaScript object into variables of the same name. Here is a simple non-React example:

function displaySong({title, artist}) {
    console.log(`${title} ${artist}`);
}

const songObject = {
    title: 'Wonderwall',
    artist: 'Oasis'
};

displaySong(songObject);
In this simpler example we create an object songObject containing two properties title and artist. We then pass this to the displaySong() function. However, note that displaySong() destructures the object into two variables, title and artist, corresponding to the properties of the same name. Likewise with React props, the props to a component become an object, which is then destructured in the component function.

So we could write the above React example using destructuring as follows:

function Greeting({firstname, lastname}) {
    return <p>Hello {firstname} {lastname}!</p>;
}

export default Greeting;
Note how the two props firstname and lastname are destructured into the respective individual variables.

Inline Styles in React

An important point about JSX is that inline styles are specified with a different syntax.

<p style={{ backgroundColor: 'yellow' }}>
The outer curly brackets indicate that we are embedding JavaScript within HTML within JSX, as before. The inner brackets indicate that we are defining a JavaScript object to represent the style, rather than ordinary CSS. The same rules as normal for accessing styles within JavaScript apply, i.e. style properties with dashes become camel case, for example
background-color
becomes
backgroundColor
.

Introduction to React State

React components use the concept of state. A React component can store data it needs to do its job in a group of state variables, which can be retrieved when methods of the component are called. This is central to how React works. We store the application's data in its state, and describe the components to be rendered using a mix of HTML and state variables. The result will be that when we update the state, the application will be automatically re-rendered to use the current values stored in the state. Thus, we do not have to manually update the UI each time the application's data changes.

For example here is a simple component which allows the user to enter a name and updates a greeting message with the most recent name that was entered. The name is stored in state.

import React from 'react';

function InteractiveGreeting() {

    const [name,setName] = React.useState("No name");

    return(
        <div>
        <h2>Enter your name</h2>
        <input id='txtName' />
        <div>Hello {name}</div>
        <input type='button' value='update' onClick={updateStateName} />
        </div>
    );

    function updateStateName() {
        setName(document.getElementById('txtName').value);
    }
}

export default InteractiveGreeting;
To explore this in detail: The line:
const[name, setName] = React.useState("No name");
sets up a state property called name and sets it up to initially contain the value "No name". It also creates a function setName() which is used to update the state; this function is created for you automatically in the background. React.useState() always returns an array containing a state variable and a matching function to update it. Note the destructuring syntax:
const [name, setName] = ....;
This causes the array returned by React.useState() to be destructured into two individual variables, name and setName.

Multiple state variables can be setup in this way, and each state variable has a corresponding set method to update that state variable. Note how the content of the div is set to the state variable name. The interesting thing here is that whenever the name property of the state changes, the div will be automatically updated, as name always refers to the current state. This shows how state can be more useful than just storing the name in a regular variable, in which case this auto-update would not happen. Essentially we are binding the div to the state variable name, ensuring that the div is always displaying the current name.

But how do we update the state? Hopefully you can see that we handle a click event on the button within the component, and call a custom function updateStateName() when it's clicked. If you look at this function (within our component), you can see that it uses setName() - the setter function we obtained from React.useState() - to update the state to the contents of the form field with the ID txtName.

So the end result will be that the div keeps in sync with whatever the user enters in the text field. This automatic update of the UI to reflect current state is possibly the most fundamental principle of React.

Event handling the React way

The above example included code to handle a click event on a button. In React, the convention used for event handling is to set up an event handler in the HTML rather than using addEventListener(). This is done in a component's JSX, not the HTML of the web page, so the actual main HTML page remains free of JavaScript.

Note also how the event is onClick, not onclick; React uses the convention for event handling of capitalising the first letter of the event type.

The handler for onClick is the updateStateName() function: as updateStateName is a variable within JSX, we have to surround it with braces.

<input type='button' value='update' onClick={updateStateName} />

Implementing an onChange() event handler

Consider this new version of the InteractiveGreeting example. As well as displaying the name in the div it also displays it in the input box.

import React from 'react';

function InteractiveGreeting2() {

    const [name,setName] = React.useState("No name");

    return(
        <div>
        <h2>Enter your name</h2>
        <input id='txtName' type='text' value={name} />
        <div>Hello {name}</div>
        <input type='button' value='update' onClick={updateStateName} />
        </div>
    );

    function updateStateName() {
        setName(document.getElementById('txtName').value);
    }
}

export default InteractiveGreeting2;

If you try this out, you'll notice that it is impossible to change the text in the text field. Why is this? The text field is tightly bound to the name property of the state, so unless the state changes, the text field will not change. More generally, any element within the component which references the state is tightly bound to the state and will not change unless the state changes. To implement an editable text field, you need to implement an onChange() event handler on the text field to update the state. The next example shows this:

import React from 'react';

function InteractiveGreeting3() {

    const [name,setName] = React.useState("No name");

    return(
        <div>
        <h2>Enter your name</h2>
        <input id='txtName' type='text' value={name} onChange={updateStateName} />
        <div>Hello {name}</div>
        <input type='button' value='update' onClick={updateStateName} />
        </div>
    );

    function updateStateName() {
        setName(document.getElementById('txtName').value);
    }
}

export default InteractiveGreeting3;

Note how the text field now has an onChange event handler, which is the updateStateName() function of the component. In updateStateName() we use the setName() method, remember that when we create the hook we obtain a series of variables, the state variable and a setter to update the state with.

Note that you must use the setter method to update the state; do not modify the state variable directly.

Displaying lists in React

Often in React, you have to loop through an array or list of data, and generate JSX from the array (for example, create an HTML list from the array). The example below shows how to do this:

....
function ListComponent() 
{
    const modules = ["WAD", "OODD", "MAD", "Databases"];
    const modulesList = modules.map(module => 
          <li key={module}>{module}</li>
    );
    return <ul>{modulesList}</ul>;
}
.... 

Here we are performing a mapping to convert an input array into an output array using a transformation defined with an arrow function. See the Mozilla documentation for more.

Here we are mapping an array of modules into an array of <li> list items containing the module. This mapped array is stored in the variable modulesList. We are then returning a <ul> containing the list items from the component.

Note also how each list item has a unique key, equal to the current module. React uses this to keep track of which item in the list is which, if items in the list are modified or deleted. We don't do this here, but nonetheless you should always give items in a rendered list of items a unique key.

Larger Example - Shopping Cart

The next example combines the discussion on state with the discussion on loops and arrays above, to implement a shopping cart by storing an array of items in state.

import React from 'react';

let itemId = 1;

function ShoppingCart({store}) {
    const [cart, setCart] = React.useState([]);


    const cartHtml = cart.map ( item => <li key={item.id}>{item.name}</li>);

    return (
            <div>

            <h1>{store} Shopping Cart</h1>

            <div>
            <h2>Add something to your shopping cart</h2>
            <fieldset>
            <input type='text' id='item' />
            <input type='button' value='Go!' onClick={addItem} />
            </fieldset>
            </div>

            Here is your shopping cart:
            <ul>
            {cartHtml}
            </ul>

            </div>
        );



    function addItem() {
        const items = structuredClone(cart);
        const newItem = {
            id: itemId++,
            name: document.getElementById('item').value
        };
        items.push(newItem);
        setCart(items);
    }
}

export default ShoppingCart;

This is quite a bit more complex.

Note how we:

Components must have a root element

Note that the JSX returned by a component function must have a single root element. Here is an example, note that MappingAppComponent contains a MapComponent and a PointsOfInterestListComponent but also contains a div to contain them both as a root element for this component.

function MappingAppComponent()  {
    return (
        <div>
        <MapComponent />
        <PointOfInterestListComponent />
        </div>
    );
}
export default MappingAppComponent;


Exercises

You should allow up to an hour to do Exercises 1-3 and two hours for Exercise 4, which requires you to create much more code from scratch.

Exercise 1

Clone the repository:

https://github.com/nwcourses/react-starter
Build via Webpack and test the Hello World example out by starting the provided Express server.

Exercise 2

Exercise 3

In your Greeting component from Exercise 2, add code to style the div according to a background colour defined in a prop colour. Modify your index.js so you pass the colour prop into the component.

Exercise 4 - AJAX search with React

Build a version of your HitTastic! AJAX site using React, step-by-step as follows:

  1. Start your project with the react-starter project, as above.
  2. Now start working on your React code. Initially, create a single component with a search box for the artist, and a button, similar to the InteractiveGreeting3 example above. Also, include a state variable containing the current artist, and a message within a <div> telling the user what artist the user entered. In this message, display the artist from the state.
  3. Next, connect the button to an event handler function within the component. The event handler should store the artist the user entered in the state variable you created in the previous step.
  4. Add another state variable to hold the AJAX search results. This should be an array, as in the shopping cart example above.
  5. Now enhance your event handler function to perform an AJAX request to your Session 1 web API (the search by artist route) and update the results <div> to contain the raw JSON from the server. How will you do this?
  6. Next, try displaying the search results in a user-readable form, rather than JSON.