Topic 5
Introduction to React
Today you will be introduced to the user-interface library React, but before doing so, you should answer this revision exercise covering some key points from Topics 3 and 4.
Exercise 1
Answer to exercise 1
- The problem is that the
document.getElementById()may returnnull, because it returnsHTMLElement | null. If we know that it exists in our application we can assert this with the non-null assertion operator:
- In this case, we are asserting non-null, so the issue is different. The problem is that
document.getElementById()returnsHTMLElement, butvalueis not a property ofHTMLElement. We have to cast it toHTMLInputElement, which does have thevalueproperty:
importstatements for client-side libraries are not understood by the browser, but they are understood by Vite, either as a dev server or a bundler.Again, these are not accepted by a browser, but they are accepted by Vite, either as a dev server or a bundler.
To allow AJAX requests from a front-end delivered from a server on another port (the Vite server on port 5173), the Express server on Port 3000 must explicitly allow the Vite server to connect using CORS.
What is React?
React (see 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 components. A component is a subsection of a web page, consisting of multiple related HTML elements (e.g. divs, form fields and buttons), which all work together to perform a particular item.
Components are designed to be reusable, so that once we've written one, we can make use of it in many projects by importing it.
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 a text field to enter the search term, a search button and a div to display the search results, all brought together as one reusable entity.
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 - like Compose, which you have all met in OODD. Like Compose, it does this through the use of state, which will be described below. Also like Compose, it is an example of a declarative UI library, in which you declare the contents of the UI and control which contents are displayed and what data they contain via the use of state.
Exploring React - Hello World
You render React into a specific page element, such as a div. Here is an example of an HTML page which includes a placeholder div to hold React components:
This is a Hello World application, which will be rendered into the root div in the above example.
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.
Explanation of the code
So what's the code example above doing?
First of all we create a root element for our React app. This is done with the
createRoot()method, part of React's DOM manipulation libraryReactDOM. This takes an existing HTML element as an argument - here, the element with the ID ofrootWe then call the
render()method of our root element and specify something which looks like a string of HTML, but does not have the quotes. This is JSX. So here the React code will render an H1 heading reading Hello World inside the element with the ID ofroot.
Preparing React code for the browser
As we saw above, React code contains JSX, an extension to JavaScript which allows you to easily render components as tags directly from JavaScript. Browsers will not understand JSX directly. We must convert the JSX to standard JavaScript using a build tool. We can use Vite.
Configuring TypeScript
Use this configuration for a React project:
Many of these options you have met before, except one, the jsx option. This is set equal to preserve. This means that tsc will not attempt to compile the JSX into plain JavaScript; this process will be done later, with Vite.
Vite and React
Vite will work easily with React as long as the Vite React plugin is also installed, and specified in Vite's configuration file vite.config.mjs, discussed below. The Vite React plugin needs to be installed:
Configuring Vite to use React
You need to configure Vite to use the React plugin. As discussed above, the Vite configuration file is vite.config.mjs. This is quite easy to understand. Create this file and add this code to it:
It is a small JavaScript file which is read by Vite on startup. We import the function defineConfig() from the vite module and then import the react() function from the @vitejs/plugin-react module. We then define a configuration object with the defineConfig() function. This object has a plugins property containing an array of all the plugins which will be used by Vite. The object is exported from our configuration file so that Vite can use it.
- To read about
vite.config.mjsin more detail, including the full range of configuration options, please see the documentation. It contains several sub-sections (accessible via the left sidebar) covering the different types of configuration options.
Installing dependencies
Install React, and react-dom and their type definitions as well as the Vite React plugin:
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. As we are using TypeScript, we will save our components with the .tsx extension.
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.jsx) 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.
Note how we export the component function. This allows us to import it from other files.
Using the component in our app
To use a component, we incorporate it into JSX by writing a tag corresponding to the function name. So we could incorporate the above component into JSX with the tag <Greeting>. Here is an example of a main.tsx making use of the component:
Note how we import the component from the appropriate module (./greeting within the components folder - note we can omit the extension) and pass in 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.:
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 in JSX if we are not using TypeScript:
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:
which would be rendered, for example, as follows:
Props in TypeScript
However, the above will not work in TypeScript. If you try, you will get the error:
We encountered this error previously in Topic 3 when looping through the JSON objects returned from the API: the parameter to our forEach arrow function was not specified with a type, and TypeScript cannot work out the type so it implicitly assumes the generic any type, and assigning a type of any without explicitly declaring it as such is a compiler error.
We solved it by defining a custom type, and that is what we will do now. Any component will accept a specific set of props, so we define this props in an interface:
and then declare the props with this type:
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 extract properties of a JavaScript object into variables of the same name. Here is a simple non-React, JavaScript (not TypeScript) example:
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 with TypeScript using destructuring as follows:
Note how the two props firstname and lastname are destructured into the respective individual variables. Note also how the type of the parameter is the same: the original PersonProps type we are destructuring.
Inline Styles in React
An important point about JSX is that inline styles are specified with a different syntax.
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
Like Compose, 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, i.e. using a declarative approach. Just like Compose, when we update the state, the component and any child components 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.
To explore this in detail: The line:
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:
This causes the array returned by 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.
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.
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:
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.
Using the event object and SyntheticEvent.target
In the examples above we have obtained the text in the text field by using document.getElementById() with the text field's id. However we do not have to do this in the onChange event handler. In any event handler, there is an event object (object of type SyntheticEvent, a React-specific event type) available to us which allows us to find out more information about the event. This includes the element which generated the event, which can be obtained with the SyntheticEvent's target property.
In the case of onChange events, the exact type of the event object is a more specific subtype of SyntheticEvent, namely ChangeEvent. Similarly, for other types of event, the exact type of the event object will be a specific subtype.
Here is an example with an onChange but no onClick handler, rewritten to use the event object and also an arrow function for the event handler, rather than a named function:
Exercise 1
Create Hello World in React. To show it's working with Vite, change the message to "Hello React World with Vite!"
Exercise 2
Create the
Greetingcomponent above (the version with first name and last name props). Save it in acomponentsfolder within yoursrcfolder. Add a third prop,age, to the above component and pass in an age frommain.tsx. Display the age in the component, e.g:Ensure that you import the component from your
main.tsx!- Change your code to use destructuring.
Try using the ternary operator (?) to display a different message within the component's JSX, either "You are an adult" or "You are not an adult", depending on whether the
agestate variable is 18+ or not. The ternary operator allows you to write a ternary expression, which is a shorthand version of anifstatement. Here is a non-React example which displays a name, or an error if the user enters a blank string:In your case, however, you should include the ternary expression as an expression within your JSX. Note the general syntax of a ternary expression:
You will need to use
parseInt()to convert between strings and numbers in order for the ternary expression to work.
Exercise 3
- In your
Greetingcomponent from Exercise 2, add code to style the div according to a background colour defined in a propcolour. Modify yourmain.tsxso you pass thecolourprop into the component. - Create a new interactive component similar to the
InteractiveGreetingexamples, above. Extend the code to contain two text fields, one for name and one for age, and store both in state variables. Add an "Update details" button which updates the state to what the user entered in the text fields. Include a "Your name is " message, showing your name. Also, include a new message reading:- "You are NOT old enough to vote" if the age is 0-17;
- "You are old enough to vote" if your age is 18 or more;
- or "Invalid age" if the age is less than 0.The message should update when the user clicks the button. Make the background of the message red if the age is less than 18, or green if the age is 18 or more.
- Implement
onChange()on the age, as shown in the example. - Add an additional field to allow the user to input a location: "Winchester", "Salisbury" or "London". This should allow the user to calculate the train fare to that location. Winchester is £3; Salisbury is £5 and London is £15. If you are under 18, you get half fare. Display the correct fare, accounting for the age, when the user clicks Go, ensuring that the destination is read in.