Chapter 15 Introduction to React
This chapter introduces the React JavaScript library. React is “a JavaScript library for building user interfaces” developed by Facebook (though it is released as open-source software). At its core, React allows you to dynamically generate and interact with the DOM, similar to what you might do with jQuery. However, React was created to make it much easier to define and manipulate lots of different parts of the DOM, and to do so quickly (in terms of computer speed). It does this by enabling you to declare a web app in terms of different components (think: “things” or “parts” of a web page) that can be independently managed—this lets you design and implement web apps at a higher level of abstraction. Moreover, components are usually associated with some set of data, and React will automatically “re-render” (show) the updated component when the data changes—and to do so in a computationally efficient manner.
React is currently the most popular “framework” for building large-scale web applications (its chief competitors being Angular and Vue.js, though there are scores of similar frameworks that are also used. Check out TodoMVC for an example of the same application in each!). This means that React is generally very well documented; there are hundreds of tutorials, videos, and examples for building React applications (of which this chapter will be yet another). Some general resources are included at the end of the chapter, but in general we recommend you start with the official documentation, particularly the set of main concepts. Note that this book more or less follows the approach used by Facebooks’s Intro to React Tutorial.
React has gone through a number of different major versions in its short life. What’s more: the “style” in which React is written has also evolved over time as new features are added to both the React library and the JavaScript language. This book introduces and emphasizes an approach that prioritizes clarity of concepts and readability of code, rather than conciseness of syntax or use of advanced options, while also attempting to reflect the current practices in React development.
React is developed and maintained by Facebook (Meta). Facebook as a company has a long history of violating the Fair Housing Act, leaking or selling personal private data, allowing the spread of misinformation, enabling genocide and in general choosing profits over safety. You may be understandably concerned about whether learning and using React implicitly supports and thus condones this company and its behavior. Whenever you adopt any software tool or library, it is worth investigating who created it (and why) and how that may impact your own willingness to use it as a dependency.
15.1 Getting Set Up: React and Vite
React is a JavaScript library similar to those discussed in previous chapters—you load the library and then can call upon its methods. However, React makes use of a significant amount of advanced and custom JavaScript syntax called JSX (described below), as well as extensive use of ES6 Modules. Thus in practice, developing React apps requires using a number of different development tools—just getting setup and started is one of the major hurdles in learning React!
Most React apps are structured using a production framework. Such frameworks collect and scaffold the different dependency libraries and build tools needed to start a React project. There are numerous such frameworks, each of which has its own opinions and restrictions on how React apps should be structured. For this course you’ll use Vite as a build tool framework. Vite (pronounced “veet”) provides a quick a simple build environment that allows you to easily write “pure” React without needing to also learn additional framwork quirks. Once you have a foundational understanding of React, you can easily explore and learn additional frameworks and tools.
Facebook previously provided an official scaffolding tool called Create React App, though that system has been abandoned (see e.g., this developer comment for some context). Older versions of this textbook and course used Create React App, so you may still see some old references to it!
Vite is a command line application that generates scaffolding (“starter code”) for a React application, as well as provides built-in scripts to run, test, and deploy your code. You can use Vite to create a new React app by running the program, specifying name of the folder where you want the app to be created, and that Vite should scaffold a React application (Vite supports multiple different web frameworks). For example, the below command will create a new folder called my-app
that contains all of the code and tools necessary to create and run a React app:
# Create a new React app project in a new `my-app` directory
# Hit 'y' when prompted!!
npm create vite@latest my-app -- --template react
# Change into new project directory to run further commands
cd my-app
# Install the build dependencies Vite has scaffolded
npm install
The
npm create <package>
command is actually a shortcut for runningnpx create-<package>
—you could equivalently writenpx create-vite@latest
, though thecreate
command is more conventional. You do not need to install thevite
(orcreate-vite
) packages globally.If you specify the current directory (
.
) as the target folder name (where “my-app” is in the above example), Vite will create a new react app in the current folder! This is useful when you want to create a React app inside an existing cloned GitHub repository.The
--template react
argument specifies to use the React “starter” template. Vite supports multiple community-defined templates. The extra--
before that argument helpsnpm
know which named argument is being passed in.
Running the Development Server
After you create a React app, you can use Vite to start up a development webserver by running the dev
script:
# Make sure you are in the project directory
cd path/to/project
# Run the development server script
npm run dev
This will start up the webserver. You’ll need to open up a browser to view the served content, by default at http://localhost:5173/. This development webserver will automatically perform the following whenever you change the source code:
It will transpile React code into pure JavaScript that can be run in the web browser (see JSX below).
It will manage and reference different JavaScript modules and dependencies, including external node modules.
It will display errors and warnings in your browser window—most errors will show up in the developer console, while fatal errors (e.g., syntax errors that crash the whole app) will be shown as an error web page. Note that the Chrome React Developer Tools will provide further error and debugging options.
It will automatically reload the page whenever you change the source code! (Though it is often good to manually refresh things when testing, just to make sure).
In order to stop the server, hit ctrl-c
on the command line.
Project Structure
A newly created React app using the Vite react
template will contain a number of different files, including the following:
The
index.html
file is the home page for your application. If you look at this file, you can see that it contains almost no HTML content—just the<head>
, the<body>
and a single<div id="root">
. That is because your entire app will be defined using React JavaScrip code; this content will then be injected into that#root
element. _All of the content of the page will be defined as JavaScript components—you’ll be creating the entire page in JavaScript files. Thus in practice, you rarely modify theindex.html
file (beyond changing the metadata such as the<title>
and favicon).The
index.html
file also loads a script/src/main.jsx
. The.jsx
extension represents JSX code (see below); Vite projects conventionally call this filemain.jsx
instead ofindex.js
. All JavaScript files are found in thesrc/
folderThe
index.html
file can also include<link>
elements in the<head>
in order to laod CSS. However, it is much preferred to instead import load any CSS content through JavaScript code (though Vite will work with either approach). All CSS files are placed in thesrc/
folder.The
src/
folder contains the source code for your React app. The app is started by themain.jsx
script, but this script imports and uses other JavaScript modules (such as the providedApp.jsx
script, which defines the “App” module). In general, you’ll code React apps by implementing components in one or more modules (e.g.,App.jsx
,AboutPage.jsx
), which will then be imported and used by themain.jsx
file. See the Components section below for details.When starting a new React app, I recommend deleting all of the content of
main.jsx
andApp.jsx
in order to start fresh.The
src/index.css
andsrc/App.css
are both CSS files used to style the entire page and the individual “App” component respectively. You do not necessarily need both files; I recommend keeping just a singleindex.css
file as you get staretd. Notice that the CSS files are imported from inside the JavaScript files (with e.g.,import './index.css'
in thesrc/main.jsx
file). This is because the Vite build system knows how to load CSS files, so you can “include” them through the JavaScript rather than needing to link them in the HTML.If you want to load an external stylesheet, such as Bootstrap or Font-Awesome, you can still do that by modifying the
public/index.html
file—but it’s better to install them as modules and import them through the JavaScript. See the documentation for Adding Bootstrap for an example.- Remember that all CSS is global—rules specified in any
.css
file will apply to the entire app (i.e., theApp.css
rules will also apply to components other than those inApp.js
). The CSS is broken into multiple files to try and keep rules organized. You can instead put all your CSS rules into a single file, using naming schema or other techniques to keep them organized. If you are interested in ways to keep the CSS from being applied globally, look into using CSS in JS.
- Remember that all CSS is global—rules specified in any
The
src/assets
folder can be used to store media assets that are imported directly; I recommend deleting this and storing assets in thepublic
folder instead. See the Vite assets guide for details.The
public/
folder is where you put assets that you want to be available to your page but aren’t loaded and compiled through the JavaScript (i.e., they’re not “source code”). For example, this is where you would put animg/
folder to hold pictures to show. Thepublic/
folder be treated as the “root” of the webpage; a file found atpublic/img/picture.jpg
would be referenced in your DOM asimg/picture.jpg
—using a path relative to thepublic/
folder. See the Vite assets guide for details.You can alternatively put assets into the
src/assets
folder, and thenimport
them into the JavaScript (treating those images as “source code”). I don’t recommend this when starting out; it’s simpler to put images into thepublic
folder and delete thesrc/assets
folder.The two config files
eslint.config.json
andvite.config.js
includes configuration information for eslint (for styling checks) and Vite respectively. You don’t need to modify either of these, but do not delete them!
Overall, you’ll mostly be working with the .jsx
and .css
files in the src/
folder as you develop a React app.
15.2 JSX
At its core, React is simply a DOM creation and rendering library—it allows you to declare new DOM elements (e.g., make a new <h1>
) and then add that element to the webpage. This is similar to the process you might do with the DOM or the jQuery library:
//Create and render a new `<h1>` element using DOM methods
const element = document.createElement('hi'); //create element
element.id = 'hello'; //specify attributes
element.classList.add('my-class');
element.textContent = 'Hello World!'; //specify content
document.getElementById('root').appendChild(element);
The React library provides a set of functions that do similar work:
//Import required functions from the React libraries
import React from 'react';
import ReactDOM from 'react-dom/client';
//Create a new `<h1>` element using React methods. This function takes
//arguments indicating what the element should be
const msgElem = React.createElement(
//html tag
'h1',
//object of attributes
{ id: 'hello', className: 'myClass' },
//content
'Hello World!'
);
//Create a "React root" out of the `#root` elemment
const root = ReactDOM.createRoot(document.getElementById('root'));
//render the React element at that root
root.render(element)
React’s React.createElement()
lets you create an element (a “React element”, not a pure DOM element—they are different!) by specifying the element type/tag, the content, and an object that maps from attribute names to values—all as a single statement. The ReactDOM.createRoot()
function creates a context in which a React element can then be rendered (shown on the screen)—usually placing that in the div#root
on the page. The root’s render()
method will then take a particular React element and insert it as the child content of the React root element . Note that root.render()
function replaces the content of the root DOM element—it’s a “replace” not an “append” operation.
- Notice that the
class
attribute is specified with theclassName
property. This is becauseclass
is a reserved keyword in JavaScript (it declares a class).
The createRoot()
function was introduced with React v18 (March 2022). Previous versions of React used the ReactDOM.render()
function directly instead of explicitly creating a root context. Using ReactDOM.render()
will cause your app to function as if using React v17, preventing some advanced features from working.
The React.createElement()
function is wordy and awkward to type, particularly if you’ll be defining a lot of DOM elements (and in React, you define the entire application in JavaScript!). Thus React lets you create and define elements using JSX. JSX is a syntax extension to JavaScript: it provide a new way of writing the language—in particular, the ability to write bare HTML tags (finally!):
//Create a new `<h1>` element using JSX syntax
const element = <h1 id="hello" className="myClass">Hello World</h1>;
//Create a "React root" out of the `#root` elemment
//then render the React element at that root
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element)
The value assigned to the element
variable is not a String (it has no quotes around it). Instead it is a syntactical shortcut for calling the React.createElement()
function—the HTML-like syntax can be automatically translated into the actual JavaScript for the method call, allowing you to write the simpler version.
Notice that there is still a semicolon (
;
) at the end of the first line. You’re not actually writing HTML, but defining a JavaScript value using syntax that mostly looks like HTML!JSX can be used to define nested elements; you are not limited to a single element:
//JSX can define nested elements const header = ( <header className="banner"> <h1>Hello world!</h1> </header> );
It’s very common to put elements on their own lines, intended as if you were writing HTML directly. If you do so, you’ll need to surround the entire JSX expression in parentheses
()
to tell the interpreter that the line break shouldn’t be read as implying a semicolon!Note that similar to XML, JSX syntax requires that all elements be closed; thus you need to use a closing slash at the end of empty elements:
//JSX elements must be closed const picture = <img src="my_picture.png" alt="a picture" />
Since JSX is not actually valid JavaScript (you can’t normally include bare HTML), you need to “translate” it into real JavaScript so that the browser can understand and execute it. This process is called transpiling, and is usually performed using a compiler program called Babel. Babel is yet another piece of the toolchain used to create React applications; however, all the details about transpiling with Babel are automatically handled by the Create React App scaffolding. “Transpile with Babel” is one of the steps performed by Webpack, which is included and run from the scripts provided by Create React App (whew!).
- Yes, chaining tools together like this is how modern web development works.
In short: you write JSX code, and when you run your app with Create React App it will be turned into regular old JavaScript code that your browser can actually understand.
Inline Expressions
JSX allows you to include JavaScript expressions (such as variables you want to reference) directly inside of the HTML-like syntax. You do this by putting the expression inside of curly braces ({}
):
const message = "Hello world!";
const element = <h1>{message}</h1>;
When the element is rendered, the expression and the surrounding braces will be replaced by the value of the expression. So the above JSX would be rendered as the HTML: <h1>Hello World</h1>
.
You can put any arbitrary expression inside of the {}
—that is, any JavaScript code that resolves to a single value (think: anything that you could assign to a variable). This can include math operations, function calls, ternary expressions, or even an anonymous function values (see the next chapter for examples):
//Including an inline expression in JSX. The expressions can be mixed directly
//into the HTML
const element = <p>A leap year has {(365 + 1) * 24 * 60} minutes!</p>;
You can use inline expressions most anywhere in JSX, including as the value of an attribute. You can even declare element attributes as an object and then use the spread operator (...
) to specify them as an inline expression.
//Define a JavaScript variable
const imgUrl = 'path/to/my_picture.png';
//Assign that variable to an element attribute
const pic = <img src={imgUrl} alt="A picture" />;
//Define an object of element attributes
const attributes = {
src: 'path/to/other_picture.png',
alt: 'Another picture'
};
//A DOM element that includes those attributes, among others.
//The spread operator applies the property names as attributes of the element.
const otherPic = <img {...attributes} className="my-class" />;
Notice that when specifying a JavaScript expression as the value of an attribute (as with the src
attribute of an <img>
), you do not include quotes around the {}
. The value of the inline expression is already a string, and it is that string that you’re assigning to the attribute. Inline expressions aren’t “copy-pasted” into the HTML-style of JSX; rather the values of those expressions are assigned to the object properties represented by the HTML-style attributes.
Importantly, the elements defined with JSX are also valid JavaScript expressions! Thus you can use inline expressions to include one element inside of another.
//Define variable content to display. One is a string, one is a React element.
const greetingString = "Good morning!";
const iconElem = <img src="sun.png" alt="A wide awake sun" />;
//Conditionally change values based on an `isEvening` value (defined elsewhere)
if(isEvening) {
greetingString = "Good evening!";
iconElem = <img src="moon.png" alt="A dozing moon" />;
}
//Include the variables inline in another React element
//Notice how the `icon` element is included as a child of the `<header>`
const header = (
<header>
<h1>{greetingString}</h1>
{iconElem}
</header>
);
If you include an array of React elements in an inline expression, those those elements will be interpreted as all being children of the parent element (siblings of each other):
//An array of React elements. Could also produce via a loop or function
const listItems = [<li>lions</li>, <li>tigers</li>, <li>bears</li>];
//Include the array as an inline expression; each `<li>` element will be
//included as a child of the `<ul>`
const list = (
<ul>
{listItems}
</ul>
);
The above sample would produce the HTML:
<ul>
<li>lions</li>
<li>tigers</li>
<li>bears</li>
</ul>
This is particularly useful when using a variable number of elements, or generating elements directly from data (see the Props and Composition section below).
As a final note: you cannot include HTML comments (<!-- -->
) inside of JSX, as this isn’t actually an element type. You also can’t use inline JavaScript comments (//
), because even multi-line JSX is still a single expression (that’s why the <header>
example above is written in parentheses). Thus in order to include a comment in JSX, you can use a JavaScript block comment (/* */
) inside of an inline expression:
const main = (
<main>
{ /* A comment: the main part of the page goes here... */ }
</main>
)
15.3 React Components
When creating React applications, you use JSX to define the HTML content of your webpage directly in the JavaScript. However, writing all of the HTML out as one giant JSX expression doesn’t provide much advantage over just putting that content in .html
files in the first place.
Instead, the React library encourages and enables you to describe your web page in terms of components, instead of just HTML elements. In this context, a Component is a “piece” of a web page. For example, in a social media application, each “status post” on a timeline might be a different Component, the “like” button may be its own Component, the “what are you thinking about?” form would be a Component, the page navigation would be a Component, and so forth. Thus you can almost think of a Component as a semantic abstraction of a <div>
element—a “chunk” of the page. But like with <div>
s, a Component can be seen as made of of other Components—you might have a “Timeline” Component that is made up of many “StatusPost” Components, and each StatusPost Component may include a “ReactionButton” Component.
Thinking about a page in terms of Components instead of HTML elements can make it much easier to design and reason about your page. You don’t need to worry about the code syntax or the specific elements to use, you can just think of your page in terms of its overall layout and content—what needs to be on the page, and how that content is organized—the same way you might mock up a web page in a graphic design program. See the documentation article Thinking in React for an example of how to design a page in terms of Components.
React enables you to implement your page in terms of explicit Components by providing a way for you to define your own XML elements! Thus you can define a <Timeline>
element, which has multiple <StatusPost>
elements as children. Components will be able to be included directly in your JSX, made up of and mixed in with standard HTML elements.
<App>
<HomePage>
<Header />
<SearchBar />
<EmployeeList>
<EmployeeListItem person="James King" />
<EmployeeListItem person="Julie Taylor" />
<EmployeeListItem person="Eugene Lee" />
</EmployList>
</HomePage>
<EmployeePage>
<Header />
...
</EmployeePage>
</App>
React components are most commonly defined using function components: JavaScript functions that return the DOM elements (the JSX) that will be rendered on the screen to show that Component:
//Define a Component representing information about a user
function UserInfoCard(props) {
//This is an everyday function; you can include any code you want here
//including variables, if statements, loops, etc
const name = "Ethel";
const descriptor = "Aardvark";
//Return a React element (JSX) that is how the Component will appear
return (
<div>
<h1>{name}</h1>
<p>Hello, my name is {name} and I am a {descriptor}</p>
</div>
)
}
//Create a new value (a React element)
//This syntax in effect "calls" the function
const infoElement = <UserInfoCard />;
//Show the element in the web page (inside #root)
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(infoElement);
This function defines a new Component (think: a new HTML element!) called UserInfoCard
. Things to note about defining this component:
A Component function must be named starting with a Capital letter (and usually in CamelCase). This is to distinguish a custom component from a “normal” React element.
If you see a function with a Capitalized name, know that it’s actually a defining a Component!
It’s also possible for function components to be defined as anonymous arrow functions assigned to
const
variables:const Example = (props) => { return <div>...</div> }
This provides some benefits in terms of scoping (because of the arrow function), but otherwise makes Components even harder to read—its not as readily obviously that this is a function and a Component. You should stick to defining component functions using the
function
keyword (and not arrow functions) for readability, but be aware of the alternate approach which is used in some libraries and examples.
Component functions take in a single argument representing the props (discussed below). As such by convention it is always named
props
(with an s!). While it’s possible to omit this argument if it isn’t used (as you can with all JavaScript functions), best practice is to always include it.A component function is a regular function like any other that you’ve written. That means that it can and often does do more than just return a single value! You can include additional code statements (variable declarations, if statements, loops, etc) in order to calculate the values you’d like to include in inline expressions in the returned JSX.
Style requirement: perform any complex logic—including functional looping such as withmap()
orfilter()
—outside of thereturn
statement. Keep thereturn
statement as simple as possible so it remains readable—and more importantly debuggable (so you can use things likeconsole.log
statements to check intermediate values before they are returned). NEVER include an inline callback function so that you have areturn
statement inside of areturn
statement!A Component function can only return a single React element (you can’t return more than one value at a time from a JavaScript function). However, that single React element can have as many children as you wish. It’s very common to “wrap” all of the content you want a component to include in a single
<div>
to return, as in the above example.If you don’t like want to add an extra div for some reason, it’s also possible to “wrap” content in a Fragment, which can be declared using a shortcut syntax that looks like an element with no name:
function FragmentExample(props) { return ( <> <p>First paragraph</p> <p>Second paragraph</p> </> ) }
Once it has been defined, a Component can then be created in JSX using XML-style syntax, just like any other element (i.e., as <UserInfoCard />
). In effect, this “calls” the function component in order to create it—you never call the function directly. You can use the created Component like any other React element, such as including it as a child of other HTML (e.g., nest it inside of a <div>
). A Component thus encapsulates a chunk of HTML, and you can almost think of it as a “shortcut” for including that HTML.
Never call a component function as a function with MyComponent()
! Always render as an element with <MyComponent/>
.
Because Components are instantiated as React elements, that means that you can use them anywhere you would use a normal HTML element—including in the returned value of another component! This is called composing components—you are specifying that one component is “composed” (made up of) others:
//A component representing a message
function HelloMessage(props) {
return <p>Hello World!</p>;
}
//A component representing another message
function GoodbyeMessage(props) {
return <h1>See ya later!</h1>;
}
//A component that is composed of (renders) other components!
function MessageList(props) {
return (
<div>
<h1>Messages for you</h1>
<HelloMessage /> {/* include a HelloMessage component */}
<GoodbyeMessage /> {/* include a GoodbyeMessage component */}
</div>
);
}
//Render an instance of the "top-level" ("outer") component
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<MessageList />);
In the above example, the MessageList
Component is composed of (renders) instances of the HelloMessage
and GoodbyeMessage
Components; when the MessageList
is displayed, the interpreter will create the two other components, rendering each in turn. In the end, all of the content displayed will be standard HTML elements—they’ve just been organized into Components.
Components are usually instantiated as empty elements, though it is possible to have them include child elements.
Notice that you can and do mix standard HTML elements and Components together; Components are just providing a useful abstraction for organizing content, logic, and data!
In practice, React applications are made up of many different Components—dozens or even hundreds depending on the size of the app! The “top-most” component is commonly called App
(since it represents the whole “app”), with the App
’s returned value instantiating further components, each of which represents a different part of the page.
The App
component will represent the entirety of the website—all of your app’s content will be defined in React components (which will be rendered as children of the <App>
). But since the React components will be inserted inside of the <div id="root">
element that is already in the index.html
file, you do not ever include <body>
or <head>
elements in your React components—those elements are already defined in the HTML file itself, so don’t get created by React!
See Props and Composition below for examples of common ways that React components are composed.
Strict Mode
If you look at the starter code generated by Vite, you will see a common pattern of “wrapping” a rendered <App>
component in a <React.StrictMode>
component:
import React from 'react';
import ReactDOM from 'react-dom/client';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
The StrictMode
component is provided by React (it’s a property of the React
object imported in the first line of the above example). Kind of like the use strict
declaration for JavaScript, this component enables a number of additional error checking that React can perform— though most of the issues it identifies are due to using out-of-date practices or errors with features not covered in this course. It can be useful, though it will cause duplicated output of some console.log()
statements and is not strictly necessary.
Component Organization
React applications will often have dozen of components, quickly growing more complex than the basic starting examples in the previous section. If you tried to define all of these components in a single index.js
file, your code would quickly become unmanageable.
Thus individual components are usually defined inside of separate modules (files), and then imported by the modules that need them—other components or the root index.js
file.
Recall that in order to make a variable or function (e.g., a Component) available to other modules, you will need to export
that component from its own module, and then import
that value elsewhere:
/* in Messages.js file */
//Export the defined components (as named exports)
export function HelloMessage() { /* ... */ }
export function GoodByeMessage() { /* ... */ }
/* in App.js file */
//Import components needed from other modules
import { HelloMessage, GoodbyeMessage } from `./Messages.js`; //named imports!
//Can also export as a _default_ export; common if the file has only one component
export default function App() {
return (
<div>
{/* Use the imported components */}
<HelloMessage />
<GoodbyeMessage />
</div>
)
}
/* in index.js file */
//Import components needed from other modules
import App from `./App.js`; //default import!
//Render the imported component
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
This structure allows you to have each component (or set of related components) in a different file, helping to organize your code.
This “export-import” structure implies a one-directional hierarchy: the
Message
components don’t know about theApp
component that will render them (they are self-contained). You should not have two files that import from each other!The
index.js
file will usually just import a single component; that file stays very short and sweet. Put all of your component definitions in other files.
It is common to use the file system to further organize your component modules, such as grouping them into different folders, as in the below example:
src/
|-- components/
|-- App.js
|-- navigation/
|-- NavBar.js
|-- utils/
|-- Alerts.js
|-- Buttons.js
index.js
Note that a module such as Alerts.js
might define a number of different “Alert” components to use; these could be imported into the NavBar.js
or App.js
modules as needed. The App.js
component would be imported by the index.js
file, which would contain the call to root.render()
.
However, it is not necessary to put each different component in a separate module; think about what kind of file organize will make it easy to find your code and help others to understand how your app is structured!
Deprecated Alternative: Class Components
In order versions and styles of React, it was alternatively possible to define components as classes (using ES6 Class syntax) that acts as a template or blueprint for that content. In particular, you define a class that inherits from the React.Component
class—this inheritance means that the class you define IS A React Component!
//Define a class component representing information about a user
class UserInfo extends React.Component {
//Components MUST override the `render()` function, which must return a
//DOM element
render() {
//This is an everyday function; you can include any code you want here
const name = "Ethel";
const descriptor = "Aardvark";
//Return a React element (JSX) that is how the component will appear
return (
<div>
<h1>{name}</h1>
<p>Hello, my name is {name} and I am a {descriptor}</p>
</div>
)
}
}
//instantiate the class as a new value (a React element)
const infoElement = <UserInfo />;
//Show the element in the web page (inside #root)
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(infoElement);
This “class component” example is identical to the previous function component example—it takes the “body” of the function component and moves that into a (required) render()
method.
Note that the
React.Component
class is often imported as a named import, so that you can refer to it directly:import {Component} from 'react'; class MyComponent extends Component { render() { //... } }
Current best practices in React are to only use function components and to not use class components; class components provide no extra functionality, and can introduce some additional complexities (particularly for handling their state). However it is good to be aware of the option in case you run across it, and thinking about components as “classes” can be useful for understanding how they are defined and used.
React.createClass()
that took in an object whose properties were functions that would act as class methods (see here for details). Although this function has been removed, it’s worth being aware of in case you come across older examples of React code that you need to interpret or adapt.
15.4 Properties (props
)
One of the main goals of using a library such as React is to support data-driven views—that is, the content that is rendered on the page can be based on some underlying data, providing a visual representation of that data. And if that underlying data changes, React can automatically and efficiently “re-render” the view, making the page dynamic.
You can specify the underlying data that a React component will be representing by specifying properties (usually just called props) for that component. Props are the “input parameters” to a component—they are the details you pass to it so that it knows what and how to render its content.
You pass a prop to a component when you instantiate it by specifying them as XML attributes (using name=value
syntax)—the props are the “attributes” of the component!
//Passing a prop called `message` with value "Hello property"
const messageA = <MessageItem message="Hello property!" />;
//Passing a prop using an inline expression
const secret = "Shave and a haircut";
const messageB = <MessageItem message={secret} />;
//A component can accept multiple props
//This component takes in a `userName` prop as well as a `descriptor` prop
const userInfo = <UserInfo userName="Ethel" descriptor="Aardvark" />;
- Prop names must be valid JavaScript identifiers, and so should be written in camelCase (like variable names).
Components are usually defined so that they expect a certain set of props—similar to how a function is defined to expect a certain set of arguments. It is possible to pass “extra” props to the Component (by specifying additional XML attributes), but if the component doesn’t expect those props it won’t do anything with them. Similarly, failing to pass an expected prop will likely result in errors—just like if you didn’t pass a function an expected argument!
For function components, the props
are received as a single argument to the function (usually called props
). This argument is a regular JavaScript object whose keys are the props’ “names”:
//Define a component representing information about a user
function UserInfo(props) {
console.log(props) //you can see this is an object!
//access the individual props from inside the argument object
const userName = props.userName;
const descriptor = props.descriptor;
//can use props for logic or processing
const userNameUpper = props.userName.toUpperCase();
return (
<div>
<h1>{userNameUpper}</h1>
<p>Hello, my name is {userName} and I am a {descriptor}</p>
</div>
)
}
const userInfo = <UserInfo userName="Ethel" descriptor="Aardvark" />;
Again, all props are stored in the argument object—you have to access them through that value.
To avoid having to type
props.___
all the time, it’s common to use object destructuring to assign all of the props to variables in a single line:function UserInfo(props) { //assign the prop object properties to variables const {userName, descriptor} = props; return <h1>{userName}</h1>; }
It’s also possible to specify the object destructuring in the argument declaration!
//The props object passed in will be destructured into two argument variables function UserInfo({userName, descriptor}) { return <h1>{userName}</h1>; }
This can help make it clear in the source code what props a component expects, though can make the code somewhat more difficult to parse (since there are so many parentheses and curly braces). However, to keep the idea of the
props
clear, all examples in this book will use a single, non-destructuredprops
object.
Importantly, props can be any kind of value! This includes basic types such as Numbers or Strings, data structures such as Arrays or Objects, or even functions or other component instances!
//Props can be of any data type!
//Pass an array as a prop!
const array = [1,2,3,4,5];
const suitcase = <Suitcase luggageCombo={array} />;
//Pass a function as a prop (like a callback)!
function sayHello() {
console.log('Hello world!');
}
const greeting = <Greeting callback={sayHello} />;
//Pass another Component as a prop (not common)!
const card = <HolidayCard message="Greetings world!" />
const gift = <Gift toMsg="Ethel", fromMsg={card} />
Indeed, passing callback functions as props to other components is a major step in designing interactive React applications. See the next chapter for more details and examples.
Props and Composition
Recall that React components are usually composed, with one component’s returned value including instances of other components (as in the MessageList
example above). Since props are specified when a component instance is created, this means that a “parent” component will need to specify the props for its children (“passing the props”). Commonly, the props for the child components will be derived from the props of the parent component:
//Defines a component representing a message in a list
function MessageItem(props) {
return <li>{props.message}</li>
}
//A component that renders a trio of messages
function MessageListTrio(props) {
const messages = props.messages; //assign prop to a local variable for convenience
return (
<div>
<h1>Messages for you</h1>
<ul>
{/* instantiate child components, passing data from own props */}
<MessageItem message={messages[0]} />
<MessageItem message={messages[1]} />
<MessageItem message={messages[2]} />
</ul>
</div>
);
}
//Define and pass a prop to the parent comment when rendering it
const messagesArray = ["Hello world", "No borders", "Go huskies!"];
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<MessageListTrio messages={messagesArray});
This creates a one-directional data flow—the data values are passed into the parent, which then passes data down to the children (not vice versa).
The component organization in the previous example is particularly common in React. In order to effectively render a list (array) of data, you should define a Component that declares how to render a single element of that list (e.g., MessageItem
). This allows you to focus on only a single problem, developing a re-usable solution. Then you define another component that represents how to render a whole list of elements (e.g., MessageList
). This component will render a child item component for each element—it just needs to set up the list, without worrying about what each item looks like!
The data set can be passed into the parent (List) component as a prop, and each individual element from that data set will be passed into an instance of the child (Item) component. In effect, the List component is tasked with taking the data set (an array of data values) and producing a set Item components (an array of Components). This kind of transformation is a mapping operation (each data value is mapped to a Component), and so can be done effecttively with the map()
method:
//Map an array of message strings to an array of components
const msgItems = props.messages.map((msgString) => {
const component = <MessageItem message={msgString} />; //pass prop down!
return component; //add this new component to resulting array
})
And because including an array of Components as an inline expression in JSX will render those elements as siblings, it’s easy to render this list from within the parent:
function MessageList(props) {
//the data: an array of strings ["", ""]
const messageStringArray = props.messages;
//the renderable content: an array of elements [<>, <>]
const msgElemArray = messageStringArray.map((msgString) => {
const component = <MessageItem message={msgString} />; //pass prop down!
return component; //add this new component to resulting array
})
return (
<div>
<h1>Messages for you</h1>
<ul>
{/* render the array of MessageList components */}
{msgElemArray}
</ul>
</div>
);
}
//Define and pass a prop to the parent comment when rendering it
const messagesArray = ["Hello world", "No borders", "Go huskies!"];
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<MessageList messages={messagesArray});
Note that the above example will issue a warning in the developer console. This is because React requires you to specify an additional prop called key
for each element in an array of components. The key
should be a unique string for each element in the list, and acts as an identifier for that element (think: what you might give it as an id
attribute). React will use the key
to keep track of those elements, so if they change over time (i.e., elements are added or removed from the array) React will be able to more efficiently modify the DOM.
If unspecified, the key
will default to the element’s index in the array, but this is considered unstable (since it will change as items are added or removed). Thus a better approach is to determine a unique value based on the data—which means that data items will need to be uniquely identifiable. The below improved version of the code changes each message from a basic String to an Object that has content
and id
properties, allowing each data value to be uniquely identified. Note that the MessageItem
component need not be changed; it still renders an <li>
showing the given message
prop!
const MESSAGE_DATA = [
{content: "Hello world", id: 1}
{content: "No borders", id: 2}
{content: "Go huskies!", id: 3}
];
function MessageList(props) {
//the data: an array of objects [{}, {}]
const messageObjArray = props.messages;
//the renderable content: an array of elements [<>, <>]
const msgElemArray = messageObjArray.map((msgObject) => {
//return a new MessageItem for each message object
//attributes are listed on their own lines for readability
return <MessageItem
message={msgObject.content}
key={msgObject.id.toString()} {/* pass in a key prop! */}
/>;
}) //end of .map()
return (
<div>
<h1>Messages for you</h1>
<ul>
{msgElemArray}
</ul>
</div>
); //end of return
}
//Define and pass a prop to the parent comment when rendering it
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<MessageList messages={MESSAGE_DATA});
This pattern enables you to effectively define your web page’s content in terms of individual components that can be composed together, allowing you to quickly render large amounts of data (without needing spend a lot of time thinking about loops!).
Resources
As mentioned above, there are a large number of different resources for learning React. Below are links to key parts of the official documentation.