As you’ve learned the fundamentals of React, you’ve seen how to use functions to create components. You can also create components using ES2015 classes. After reading and practicing, you should be able to:
this
keyword within event handlersSyntheticEvent
object is and the role it plays in handling eventsonChange
events for multiple <input>
elements<textarea>
element to a form<select>
element to a formcomponentDidMount
, componentDidUpdate
, and componentWillUnmount
componentDidMount
component lifecycle method to fetch data from an APIWhile creating a new React project using Create React App is easy to do, the application that the default template generates contains items and content that aren’t essential for learning React. Distilling the generated application down to its essential items and content removes all distractions and allows you to focus on the basics.
When you finish this article, you should be able to:
Deleting files from the standard Create React App template can get tedious. To help save you time, App Academy has a custom template that you can use with Create React App to generate a simple React application!
Just run the following command:
Feel free to change my-app
to whatever you’d like. Once the command completes, browse to the generated application folder and run npm start
to start your application!
If you still just want to use the template that comes with Create React App and want to delete the files yourself, here’s that guide for you to follow.
To start, use Create React App to create a new application:
Then open the simple-project
folder in your code editor.
public
folderIn the public
folder, you’re only going to keep the index.html
file, so remove all of the following:
favicon.ico
robots.txt
logo192.png
logo512.png
manifest.json
The favicon.ico
and robots.txt
files are useful for applications that will actually be released and used by actual users, but not necessary for learning projects. The logo192.png
and logo512.png
image files are referenced in the manifest.json
file and are the icons that browsers use in various contexts (home screen, application menu, etc.) The manifest.json
file provides the metadata that’s used when your web app is installed on a user’s mobile device or desktop.
Now you can simplify the content of the index.html
to this:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>React App</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
To get to the above HTML, you can:
html:5
Emmet command to generate the boilerplate HTML and add a <div>
element with an id
attribute set to root
(this is the element that your React application will render to).src
folderIn the src
folder, you’re going to keep the following files:
App.js
index.css
index.js
And remove all of the following:
App.css
App.test.js
logo.svg
serviceWorker.js
setupTests.js
The App.css
file is where you’d add styles that are scoped to the App
component and the App.test.js
file is where you’d write unit tests for the App
component. While you’re learning React, styling components and writing unit tests won’t be a focus. If you want to add some basic styles for your application, you can use the index.css
file to add global styles.
The logo.svg
file was part of the visual design of the generated application. The serviceWorker.js
file contains code that registers a service worker which allows the application load faster on subsequent visits if/when the application is released into production. The setupTests.js
file contains a single import statement for a module that helps make it easier to write unit test assertions on DOM nodes.
Now you can update the contents of the App.js
, index.css
, and index.js
files to the following:
// ./src/App.js
import React from 'react';
function App() {
return (
<h1>Hello world!</h1>
);
}
export default App;
// ./src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
For your reference, here’s a repo containing the final simple React project: react simple project
In this article, you learned how to:
As you’ve learned the fundamentals of React, you’ve seen how to use functions to create components to develop the user interface for a frontend application. Unsurprisingly, as with most things related to software development, there’s more than one way to create components in React.
When you finish this article, you should be able to:
Up to this point, you’ve written components using functions:
// ./src/Message.js
import React from 'react';
const Message = (props) => {
return (
<div>{props.text}</div>
);
};
export default Message;
But React also allows you to create components using ES2015 classes. Here’s the above function component rewritten as a class component:
// ./src/Message.js
import React from 'react';
class Message extends React.Component {
render() {
return (
<div>{this.props.text}</div>
);
}
}
export default Message;
Every class component must extend (i.e. inherit) from React.Component
and have a render
method that returns the element(s) to render for the component.
Class components are used just like function components:
// ./src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import Message from './Message';
ReactDOM.render(
<React.StrictMode>
<Message text='Hello world!' />
</React.StrictMode>,
document.getElementById('root')
);
From just the above JSX code in the index.js
file, you can’t tell if the <Message>
component is written as a function or class component. Which approach that’s used is an internal implementation detail of the component. In fact, you can switch back and forth between the two approaches provided that you aren’t using a feature that’s only available in class components (more about this in just a bit).
Notice from this example of using the Message
class component that props are set on class components just like you do with function components:
To access props within a class component, use the this.props
property:
If your class component defines a constructor
method that needs access to props, define a props
parameter:
class Message extends React.Component {
constructor(props) {
super(props);
// TODO Initialize state, etc.
}
render() {
return (
<div>{this.props.text}</div>
);
}
}
Just be sure to call the super
method and pass in the props
parameter! Failing to do so will result in the following error:
ReferenceError: Must call super constructor in derived class before accessing
'this' or returning from derived constructor
Remember, a component, whether it’s a function or class component, should never change its own props.
In the early days of React, before using ES2015 classes was commonplace, components were usually created using the React.createclass
function. You’ll still sometimes see this syntax in the wild. If in the future you ever find yourself needing to create a component using the React.createclass
function when targeting newer versions of React, you’ll need to install a separate React module named create-react-class
.
So far, the above example class component behaves exactly as its function component counterpart. This might leave you wondering why you’d want or need to create a class component.
One of the two reasons why you would use a Class component over a Function component is to add and manage local or internal state to your component. The second main reason to use a Class component is to use a component’s lifecycle methods. The following sections will focus on how to add and manage a component’s state. You’ll learn more about a component’s lifecycle (and the associated component lifecycle methods) later in a future reading.
In contrast to props which are provided by the consumer or caller of the component, state is data that’s internal to a component. State is owned by the component where it’s defined and used. That’s why we say it’s “internal” or “local” to that component. Whereas props are not to be changed by a component, state is intended to be updated or mutated (you’ll see how to update state in just a bit). Together, props and state represent the data that’s used to determine how the component should behave and render.
State should only be used when it’s absolutely necessary. If a bit of data is never going to change or if it is needed across the entire application, use props instead.
When you’re just learning React, it can be challenging to know when it’s okay to use state and when it’s not. State is often used when creating components that retrieve data from APIs or render forms. You’ll see examples of those kinds of components later in this lesson. To start, we’ll look at simple, contrived example of using state in just a bit.
Function components are the simplest way to declare a component. If a component doesn’t need to use state or lifecycle methods, it should be written as a function component. A new feature in React, hooks, levels the playing field between function and class components, so that everything you can do in a class component can now be done with function components. You’ll learn about React hooks in a future lesson.
When creating a stateful class component, you can use a class constructor
method to initialize the this.state
object.
Here’s a RandomQuote
component that initializes two state properties, this.state.quotes
and this.state.currentQuoteIndex
, within its constructor
method:
// ./src/RandomQuote.js
import React from 'react';
class RandomQuote extends React.Component {
constructor() {
super();
const quotes = [
'May the Force be with you.',
'There\'s no place like home.',
'I\'m the king of the world!',
'My mama always said life was like a box of chocolates.',
'I\'ll be back.',
];
this.state = {
quotes,
currentQuoteIndex: this.getRandomInt(quotes.length),
};
}
getRandomInt(max) {
return Math.floor(Math.random() * Math.floor(max));
}
render() {
return (
<div>
<h2>Random Quote</h2>
<p>{this.state.quotes[this.state.currentQuoteIndex]}</p>
</div>
);
}
}
export default RandomQuote;
Notice in the render
method, that the state properties are being accessed using this.state.quotes
and this.state.currentQuoteIndex
.
If you’re following along, be sure to update your application’s entry point to import and render the RandomQuote
component:
// ./src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import RandomQuote from './RandomQuote';
ReactDOM.render(
<React.StrictMode>
<RandomQuote />
</React.StrictMode>,
document.getElementById('root')
);
If you run your application (i.e. npm start
) and view it in the browser, you’ll one of the five quotes displayed. Refreshing the page will display a new random quote.
Sometimes the same quote will be displayed more than once in a row. You’ll see in a bit how to fix that bug.
Assuming you have the React Developer Tools installed, open up your browser’s developer tools and view the “Components” tab. On the left, you can select the RandomQuote
component to view its current props and state values on the right.
Remember earlier when we said that state should only be used if the data is going to change? Currently, the current quote index doesn’t change after it’s been initialized in the constructor
method. Using state to track this value would make more sense if there was a way to trigger the component to update the current quote index.
To do this, add a <button>
element just below the <p>
element with the following attributes and content:
<button type='button' onClick={this.changeQuote}>Change Quote</button>
Notice the onClick={this.changeQuote}
bit? This is how you add an event listener for the onclick
event. this.changeQuote
is the event handler method and onClick
is the event to listen for.
When adding event listeners, be sure to camelCase the event name (i.e.
onClick
instead ofonclick
) and pass a reference to the event handler method instead of calling it (i.e.this.changeQuote
instead ofthis.changeQuote()
). You’ll learn more about handling events later in this lesson.
Now add the changeQuote
event handler method to the RandomQuote
class:
changeQuote = () => {
const newIndex = this.getRandomInt(this.state.quotes.length);
this.setState({
currentQuoteIndex: newIndex,
});
}
Did you notice the slightly odd looking class property syntax (i.e.
changeQuote = () => { ... }
) that’s being used to define thechangeQuote
method? Using this experimental syntax for defining a class property in combination with an arrow function ensures that you can reliably use thethis
keyword within the method. You’ll learn more about this coding pattern for defining event handlers later in this lesson.
The changeQuote
event handler calls the this.getRandomInt
method to get a new random integer and then calls the this.setState
method to update the component’s state. The this.setState
method accepts an object literal containing the state properties to update.
After updating the state, React re-renders the component and displays the new quote (provided that the current quote index actually changed). Because calling the this.setState
method triggers a re-render, it should not be called from within the render
method, as that would trigger an infinite loop.
Notice that the object literal passed into the this.setState
method only contains the state property that needs to be updated. The this.setState
method merges state updates into the current state object, so you only need to provide the state properties that need to be updated.
Now, instead of refreshing the page, you can click the “Change Quote” button to display a new random quote!
For your reference, here’s the updated RandomQuote
component:
// ./src/RandomQuote.js
import React from 'react';
class RandomQuote extends React.Component {
constructor() {
super();
const quotes = [
'May the Force be with you.',
'There\'s no place like home.',
'I\'m the king of the world!',
'My mama always said life was like a box of chocolates.',
'I\'ll be back.',
];
this.state = {
quotes,
currentQuoteIndex: this.getRandomInt(quotes.length),
};
}
changeQuote = () => {
const newIndex = this.getRandomInt(this.state.quotes.length);
this.setState({
currentQuoteIndex: newIndex,
});
}
getRandomInt(max) {
return Math.floor(Math.random() * Math.floor(max));
}
render() {
return (
<div>
<h2>Random Quote</h2>
<p>{this.state.quotes[this.state.currentQuoteIndex]}</p>
<button type='button' onClick={this.changeQuote}>Change Quote</button>
</div>
);
}
}
export default RandomQuote;
You should always use the this.setState
method to update state rather than setting the this.state
property directly:
changeQuote = () => {
const newIndex = this.getRandomInt(this.state.quotes.length);
// Don't set the `this.state` property directly
// anywhere outside of the `constructor` method!
// this.state = {
// currentQuoteIndex: newIndex,
// };
// Always use the `this.setState` method to update state.
this.setState({
currentQuoteIndex: newIndex,
});
}
Reassigning this.state
alone won’t trigger re-rendering, leaving your component out of sync.
If you used Create React App to create your application and your application is currently running (using the npm start
command), you’ll receive a warning in the terminal if you reassign this.state
outside of the constructor:
When testing the RandomQuote
component, you might have noticed that sometimes the same quote will display more than once. This is occurring because the this.getRandomInt
method is returning a random integer that’s the same as the current quote index value. We can fix this bug by calling the this.getRandomInt
method until we get a random integer that’s different from the current quote index value.
On the surface, this appears to be simple fix–just use a loop to call the this.getRandomInt
method until you get a random integer that’s different from the current quote index:
changeQuote = () => {
const { quotes, currentQuoteIndex } = this.state;
let newIndex = -1;
do {
newIndex = this.getRandomInt(quotes.length);
} while (newIndex === currentQuoteIndex);
this.setState({
currentQuoteIndex: newIndex,
});
}
The problem with the above solution is that it doesn’t take into account that state updates are handled asynchronously by React. When the currentQuoteIndex
value is retrieved from state (at the start of the method block), you’re not guaranteed that it’s the latest value. There could be a state update that hasn’t been applied yet.
To safely update state based upon the previous state, pass an anonymous method to the this.setState
method (instead of an object literal) that defines two parameters, state
and props
, and returns an object literal containing the state properties to update. The state
and props
parameters give you safe, predictable access to the previous state and prop values:
changeQuote = () => {
this.setState((state, props) => {
const { quotes, currentQuoteIndex } = state;
let newIndex = -1;
do {
newIndex = this.getRandomInt(quotes.length);
} while (newIndex === currentQuoteIndex);
return {
currentQuoteIndex: newIndex,
};
});
}
Now, if you retest your application, clicking the “Change Quote” button will display a different random quote every time!
Currently, the list of quotes doesn’t change once it’s initialized in the constructor
method:
constructor() {
super();
const quotes = [
'May the Force be with you.',
'There\'s no place like home.',
'I\'m the king of the world!',
'My mama always said life was like a box of chocolates.',
'I\'ll be back.',
];
this.state = {
quotes,
currentQuoteIndex: this.getRandomInt(quotes.length),
};
}
Given this, it makes more sense for the quotes to be a prop value. Changing the quotes to a prop would also give the consumer or caller of the RandomQuote
component the ability to customize the list of quotes.
Here’s an updated version of the RandomQuote
component that defines the list of quotes as a prop:
// ./src/RandomQuote.js
import React from 'react';
class RandomQuote extends React.Component {
constructor(props) {
super(props);
this.state = {
currentQuoteIndex: this.getRandomInt(props.quotes.length),
};
}
changeQuote = () => {
this.setState((state, props) => {
const { currentQuoteIndex } = state;
const { quotes } = props;
let newIndex = -1;
do {
newIndex = this.getRandomInt(quotes.length);
} while (newIndex === currentQuoteIndex);
return {
currentQuoteIndex: newIndex,
};
});
}
getRandomInt(max) {
return Math.floor(Math.random() * Math.floor(max));
}
render() {
return (
<div>
<h2>Random Quote</h2>
<p>{this.props.quotes[this.state.currentQuoteIndex]}</p>
<button type='button' onClick={this.changeQuote}>Change Quote</button>
</div>
);
}
}
export default RandomQuote;
Notice that the constructor
method now defines a props
parameter and passes that parameter into the super
method call:
constructor(props) {
super(props);
this.state = {
currentQuoteIndex: this.getRandomInt(props.quotes.length),
};
}
The changeQuote
and render
methods were also updated to reference the quotes using this.props.quotes
instead of this.state.quotes
.
Now, in the index.js
file, the quotes to randomly display can be passed into the RandomQuote
component:
// ./src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import RandomQuote from './RandomQuote';
const quotes = [
'Toto, I\'ve a feeling we\'re not in Kansas anymore.',
'Here\'s looking at you, kid.',
'There\'s no crying in baseball!',
'Elementary, my dear Watson.',
'Rosebud.',
];
ReactDOM.render(
<React.StrictMode>
<RandomQuote quotes={quotes} />
</React.StrictMode>,
document.getElementById('root')
);
Unfortunately, now the consumer or caller of the component must set the quotes
prop or the component will throw an error. You can retain the previous behavior by defining a default value for the quotes
prop using the defaultProps
static property:
import React from 'react';
class RandomQuote extends React.Component {
// Code removed for brevity.
}
RandomQuote.defaultProps = {
quotes: [
'May the Force be with you.',
'There\'s no place like home.',
'I\'m the king of the world!',
'My mama always said life was like a box of chocolates.',
'I\'ll be back.',
],
};
export default RandomQuote;
The default quotes
prop value will be used if the consumer or caller of the RandomQuote
component doesn’t provide a value for the quotes
prop.
Now the RandomQuote
component can be used without having to provide the quotes
prop:
// ./src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import RandomQuote from './RandomQuote';
ReactDOM.render(
<React.StrictMode>
<RandomQuote />
</React.StrictMode>,
document.getElementById('root')
);
In this article, you learned how to:
Event handling is a key part of any dynamic application; without it, you wouldn’t be able to respond to user actions. As with most things in React, how you add event listeners and handle events is different from how you’d do it in vanilla JavaScript, it also manages to feel familiar.
In an earlier article, you saw an example of handling button click events. In this article you’ll deepen your understanding how to handle events in React components.
When you finish this article, you should be able to:
this
keyword within event handlers; andSyntheticEvent
object is and the role it plays in handling events.To add an event listener to an element, define a method to handle the event and associate that method with the element event you want to listen for:
// ./src/AlertButton.js
import React from 'react';
class AlertButton extends React.Component {
showAlert = () => {
window.alert('Button clicked!');
}
render() {
return (
<button type='button' onClick={this.showAlert}>Click Me</button>
);
}
}
export default AlertButton;
In the above example, the showAlert
method is the event handler, which simply calls the window.alert
method to display the text “Button clicked!” within a browser alert dialog. The showAlert
event handler is added as a listener for the <button>
element’s click event using the onClick
attribute (i.e. onClick={this.showAlert}
).
When adding event listeners, be sure to camelCase the event name (i.e. onClick
instead of onclick
) and pass a reference to the event handler method instead of calling it (i.e. this.showAlert
instead of this.showAlert()
).
Also notice the slightly odd looking class property syntax (i.e. showAlert = () => { ... }
) that’s used to define the showAlert
method. Using this experimental syntax for defining a class property in combination with an arrow function ensures that you can reliably use the this
keyword within the event handler method. We’ll exam this issue in more detail in just a bit.
See the official React documentation for a list of the supported events.
Within the browser, HTML element events often have default behavior associated with them. For example, clicking an <a>
element will navigate to the resource indicated by the anchor element’s href
attribute or clicking a <button>
element that’s contained with a form will submit the form.
When handling button clicks in the previous example, nothing special had to be done to prevent the event’s default behavior from interfering with our intended action because a <button>
element of type button
doesn’t have any default behavior associated with it.
Consider the following example though:
// ./src/NoDefaultSubmitForm.js
import React from 'react';
class NoDefaultSubmitForm extends React.Component {
submitForm = () => {
window.alert('Handling form submission...');
}
render() {
return (
<form onSubmit={this.submitForm}>
<button>Submit</button>
</form>
);
}
}
export default NoDefaultSubmitForm;
In this example, a <button>
element without a type
attribute is rendered within a <form>
element. By default, this button will submit the form when clicked. This has the unintended consequence of reloading the page when the button is clicked, instead of allowing the this.submitForm
event handler method to handle the form submission.
In an actual React application, the
this.submitForm
event handler method would likely use the browser’s Fetch API to send aPOST
orPUT
request to a REST API when the form is submitted. To keep this example as simple as possible, thewindow.alert
method is used to display the text “Handling form submission…”.
To keep the default form submission from occurring, the event handler method can be updated to this:
Notice that a parameter named e
has been added to the anonymous method definition. The e
parameter references an event object that’s the form submission event being handled. The e
event object provides a method named preventDefault
that when called, prevents the event’s default action.
The
e
parameter is aSyntheticEvent
object type. You’ll learn more about this object type in just a bit.
this
in event handlersEarlier, it was mentioned that the class property syntax (i.e. showAlert = () => { ... }
) was being used in combination with an arrow function so that the this
keyword could be reliably used within an event handler method. To understand why this coding pattern is needed, let’s stray from the “happy path” and make things break.
Here’s the example of a button click event handler again that correctly defines the showAlert
event handler method:
// ./src/AlertButton.js
import React from 'react';
class AlertButton extends React.Component {
showAlert = () => {
window.alert('Button clicked!');
}
render() {
return (
<button type='button' onClick={this.showAlert}>Click Me</button>
);
}
}
export default AlertButton;
To see what this
references in the showAlert
event handler method, you can replace the call to the window.alert
method with a call to the console.log
method to print this
to the console:
Now when the button is clicked, you’ll see the AlertButton
component printed to the console:
To break the this
keyword, you can rewrite the showAlert
event handler method to be a regular class method:
Now when the button is clicked, you’ll see undefined
printed to the console:
this
keywordTo understand why this
is undefined
when an event handler method is defined as a class method, take a look at the following example:
class Person {
constructor() {
this.name = 'Jane Smith';
}
displayName() {
console.log(this.name);
}
}
const p = new Person();
// Calling the method on the instance
// works as expected.
p.displayName(); // Jane Smith
// Storing a reference to the method in a variable
// and calling the method using the variable
// breaks the `this` keyword's implicit binding
// to the class instance.
const displayName = p.displayName;
displayName(); // TypeError: Cannot read property 'name' of undefined
The first time that the displayName
method is called, it’s called directly on p
, the instance of the Person
class. “Jane Smith” is printed to the console because the this
keyword is implicitly bound to the instance of the class allowing the name
property on the instance to be found and passed to the console.log
method.
The second time that the displayName
method is called, a reference to the class method is stored in a variable and the method is called using the variable. This breaks the this
keyword’s implicit binding to the instance of the class (i.e. p
) resulting in the TypeError
because this
is undefined
.
The bind
method can be used to explicitly bind the displayName
class method to the p
class instance. The bind
method returns a function that’s bound to the passed in object. Now the displayName
variable can be successfully called to display the person’s name in the console:
Even though this is a simple, contrived example, it accurately models what is happening with the React component’s event handler method. When adding an event listener to a React element, you associate an event handler method with the element event you want to listen for by passing a reference to the event handler method:
<button type='button' onClick={this.showAlert}>Click Me</button>
Passing the reference to the this.showAlert
class method to the onClick
attribute breaks the this
keyword’s implicit binding to the instance of the class (i.e. the instance of the AlertButton
component).
The bind
method, just like was done with the above Person
class example, can be used in a React component constructor
method to explicitly bind event handler methods to the component instance:
import React from 'react';
class AlertButton extends React.Component {
constructor() {
super();
this.showAlert = this.showAlert.bind(this);
}
showAlert() {
console.log(this);
}
render() {
return (
<button type='button' onClick={this.showAlert}>Click Me</button>
);
}
}
export default AlertButton;
To review, the pattern of defining an event handler method using a class property in combination with an arrow function looks like this:
What’s not apparent from this example is that the class property syntax, which allows you to define class properties (or fields as they’re sometimes called) outside of the constructor
method, is an experimental syntax. Experimental JavaScript syntax is syntax that’s been proposed to add to ECMAScript (the scripting-language specification for JavaScript) but hasn’t officially been added to the language specification yet.
While some browsers support class property syntax, other browsers don’t. To reliably use class property syntax, your JavaScript code needs to be converted, or transpiled, into syntax that’s broadly supported by browsers.
When using Create React App to create a React application, Babel is configured on your behalf to transpile your JavaScript code (including JSX) into a version of JavaScript that’s broadly supported. When you run the application using npm start
, the AlertButton
component is transpiled by Babel into the following code:
class AlertButton extends react__WEBPACK_IMPORTED_MODULE_0___default.a.Component {
constructor(...args) {
super(...args);
this.showAlert = () => {
console.log(this);
};
}
render() {
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("button", {
type: "button",
onClick: this.showAlert,
__self: this,
__source: {
fileName: _jsxFileName,
lineNumber: 24,
columnNumber: 7
}
}, "Click Me");
}
}
Notice how the showAlert
class property definition has been moved into the constructor
method? The value of the showAlert
property is set to the arrow function that defines the event handler method. Since arrow functions don’t have their own context, the event handler method inherits the surrounding lexical context, which is the constructor
method’s context. That results in the this
keyword within the arrow function referring to the instance of the component that’s being initialized by the constructor
method.
An arrow function’s inherited context can’t be lost or changed. When the event handler method is called later on, when the button is clicked, the this
keyword remains correctly bound to the instance of the component.
You’ll learn more about transpilation and Babel in a future lesson. To read more about Babel and its support for the proposed class property syntax, see this page.
Feel free to use either approach, class properties and arrow functions or the bind
method, to ensure that the this
keyword can be reliably used in your event handler methods. Just be sure that whatever approach you or your team has decided to use, that you follow it consistently. Doing so will make it easier to read and maintain your code.
SyntheticEvent
objectEarlier, an example was shown on how to prevent the default form submission from occurring when handling the onSubmit
form event:
// ./src/NoDefaultSubmitForm.js
import React from 'react';
class NoDefaultSubmitForm extends React.Component {
submitForm = (e) => {
e.preventDefault();
window.alert('Handling form submission...');
}
render() {
return (
<form onSubmit={this.submitForm}>
<button>Submit</button>
</form>
);
}
}
export default NoDefaultSubmitForm;
Notice that the submitForm
event handler method defines a parameter named e
which references an event object that’s the form submission event being handled.
In a React application, event objects are not the native browser event object types that you’d normally interact with when handling events using JavaScript in the browser. Instead, they’re instances of the React SyntheticEvent
object type.
An instance of the React SyntheticEvent
object type wraps the native browser event object to normalize events across browser vendors. The SyntheticEvent
object type follows the W3C spec for UI events, so you can use synthetic event objects just like you would if they were the native browser event objects. This gives you, the developer, a consistent, predictable experience working with events without having to worry about which browser your application is running within.
For your reference, the SyntheticEvent
object type has the following attributes:
boolean bubbles
boolean cancelable
DOMEventTarget currentTarget
boolean defaultPrevented
number eventPhase
boolean isTrusted
DOMEvent nativeEvent
void preventDefault()
boolean isDefaultPrevented()
void stopPropagation()
boolean isPropagationStopped()
void persist()
DOMEventTarget target
number timeStamp
string type
Notice that a synthetic event object defines a property named nativeEvent
. This property gives you access to the underlying native browser event, though you’ll rarely (if ever) need to access it.
In this article, you learned how to:
this
keyword within event handlers; andSyntheticEvent
object is and the role it plays in handling events.As you’ve learned in earlier lessons, HTML forms are an essential and ubiquitous part of the web. Forms are used to search, create resources (i.e. account, posts), update resources, and more. Learning how to create forms using React is an invaluable skill for you to learn and practice.
When you finish this article, you should be able to:
onChange
events for multiple <input>
elements;<textarea>
element to a form;<select>
element to a form; andTo learn how to create an HTML form in React, you’ll create a ContactUs
class component that’ll contain a simple “Contact Us” form. The form will initially contain just three fields:
render
methodTo start, add a class component named ContactUs
and define the render
method to render the HTML form:
// ./src/ContactUs.js
import React from 'react';
class ContactUs extends React.Component {
render() {
return (
<div>
<h2>Contact Us</h2>
<form>
<div>
<label htmlFor='name'>Name:</label>
<input id='name' type='text' />
</div>
<div>
<label htmlFor='email'>Email:</label>
<input id='email' type='text' />
</div>
<div>
<label htmlFor='phone'>Phone:</label>
<input id='phone' type='text' />
</div>
<div>
<button>Submit</button>
</div>
</form>
</div>
);
}
}
export default ContactUs;
So far, there’s nothing particularly interesting about this form. The only thing that looks different from regular HTML is that the <label>
element’s for
attribute is htmlFor
in React.
There are a variety of ways to structure the HTML for forms. The above form layout is compatible with the form CSS classes available in the Bootstrap CSS framework. While we won’t be applying any styles to the form in this article, the layout that we’ll use will make it easy to use Bootstrap at any point.
If you’re following along, be sure to update your React application’s entry point to render the ContactUs
component:
// ./src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import ContactUs from './ContactUs';
ReactDOM.render(
<React.StrictMode>
<ContactUs />
</React.StrictMode>,
document.getElementById('root')
);
At this point, you can run your application (npm start
) and view the form in the browser. You can even fill out the form, but currently the component doesn’t initialize or update any state.
To add state to the ContactUs
component, you’ll add a constructor
method that’ll initialize the this.state
object with three properties: name
, email
, and phone
. Then in the render
method you’ll retrieve the name
, email
, and phone
values from state and use them to set the value
attributes on the corresponding form field <input>
elements:
// ./src/ContactUs.js
import React from 'react';
class ContactUs extends React.Component {
constructor() {
super();
this.state = {
name: '',
email: '',
phone: '',
};
}
render() {
const { name, email, phone } = this.state;
return (
<div>
<h2>Contact Us</h2>
<form>
<div>
<label htmlFor='name'>Name:</label>
<input id='name' type='text' value={name} />
</div>
<div>
<label htmlFor='email'>Email:</label>
<input id='email' type='text' value={email} />
</div>
<div>
<label htmlFor='phone'>Phone:</label>
<input id='phone' type='text' value={phone} />
</div>
<div>
<button>Submit</button>
</div>
</form>
</div>
);
}
}
export default ContactUs;
Next, when a form field element value is changed, the associated component state property needs to be updated. Adding or removing a character within an <input>
element raises the onChange
event, which makes it a natural choice for keeping the component state in sync:
Remember that when an event is raised, the associated event handler method is called and passed an instance of React’s SyntheticEvent
object type. Here’s the nameOnChange
event handler method that’s associated with the above “Name” form field:
A reference to the element that raised the event is available through the SyntheticEvent
object’s target
property. Using the reference to the form field element, you can retrieve the current value like this:
With the current form field value in hand, call the this.setState
method to update the corresponding state value:
With a little refactoring, you can condense the event handler method to a single line of code:
Using the same approach to add an onChange
event handler to the “Email” and “Phone” form fields gives you this:
// ./src/ContactUs.js
import React from 'react';
class ContactUs extends React.Component {
constructor() {
super();
this.state = {
name: '',
email: '',
phone: '',
};
}
nameOnChange = (e) => {
this.setState({ name: e.target.value });
}
emailOnChange = (e) => {
this.setState({ email: e.target.value });
}
phoneOnChange = (e) => {
this.setState({ phone: e.target.value });
}
render() {
const { name, email, phone } = this.state;
return (
<div>
<h2>Contact Us</h2>
<form>
<div>
<label htmlFor='name'>Name:</label>
<input id='name' type='text' onChange={this.nameOnChange} value={name} />
</div>
<div>
<label htmlFor='email'>Email:</label>
<input id='email' type='text' onChange={this.emailOnChange} value={email} />
</div>
<div>
<label htmlFor='phone'>Phone:</label>
<input id='phone' type='text' onChange={this.phoneOnChange} value={phone} />
</div>
<div>
<button>Submit</button>
</div>
</form>
</div>
);
}
}
export default ContactUs;
If you view the form again in the browser and open the React Developer Tools, you can see the component’s state update as you type within each of the form fields (i.e the <input>
elements).
Now that the ContactUs
component is initializing and updating state when form field values are changed, it’s time to handle form submissions! To start, add an onSubmit
event handler to the form and within the onSubmit
event handler prevent the default behavior so that the page doesn’t reload:
onSubmit = (e) => {
// Prevent the default form behavior
// so the page doesn't reload.
e.preventDefault();
}
Then retrieve the name
, email
, and phone
values from state and use those values to create a new contactUsInformation
object literal:
onSubmit = (e) => {
// Prevent the default form behavior
// so the page doesn't reload.
e.preventDefault();
// Retrieve the contact us information from state.
const { name, email, phone } = this.state;
// Create a new object for the contact us information.
const contactUsInformation = {
name,
email,
phone,
submittedOn: new Date(),
};
// For now, just log the contact us information to the console
// though ideally, we'd persist this information to a database
// using a REST API.
console.log(contactUsInformation);
}
Notice that an additional property, submittedOn
, is being added to the contactUsInformation
object literal to indicate the date/time that the information was submitted. Ideally, the contactUsInformation
object would be persist to a database using a REST API, but for now, you’ll just log the object to the console.
Now that the form submission has been processed, call the this.setState
method to reset the name
, email
, and phone
values:
onSubmit = (e) => {
// Prevent the default form behavior
// so the page doesn't reload.
e.preventDefault();
// Retrieve the contact us information from state.
const { name, email, phone } = this.state;
// Create a new object for the contact us information.
const contactUsInformation = {
name,
email,
phone,
submittedOn: new Date(),
};
// For now, just log the contact us information to the console
// though ideally, we'd persist this information to a database
// using a REST API.
console.log(contactUsInformation);
// Reset the form state.
this.setState({
name: '',
email: '',
phone: '',
});
}
Putting all of that together gives you this:
// ./src/ContactUs.js
import React from 'react';
class ContactUs extends React.Component {
constructor() {
super();
this.state = {
name: '',
email: '',
phone: '',
};
}
nameOnChange = (e) => {
this.setState({ name: e.target.value });
}
emailOnChange = (e) => {
this.setState({ email: e.target.value });
}
phoneOnChange = (e) => {
this.setState({ phone: e.target.value });
}
onSubmit = (e) => {
// Prevent the default form behavior
// so the page doesn't reload.
e.preventDefault();
// Retrieve the contact us information from state.
const { name, email, phone } = this.state;
// Create a new object for the contact us information.
const contactUsInformation = {
name,
email,
phone,
submittedOn: new Date(),
};
// For now, just log the contact us information to the console
// though ideally, we'd persist this information to a database
// using a REST API.
console.log(contactUsInformation);
// Reset the form state.
this.setState({
name: '',
email: '',
phone: '',
});
}
render() {
const { name, email, phone } = this.state;
return (
<div>
<h2>Contact Us</h2>
<form onSubmit={this.onSubmit}>
<div>
<label htmlFor='name'>Name:</label>
<input id='name' type='text' onChange={this.nameOnChange} value={name} />
</div>
<div>
<label htmlFor='email'>Email:</label>
<input id='email' type='text' onChange={this.emailOnChange} value={email} />
</div>
<div>
<label htmlFor='phone'>Phone:</label>
<input id='phone' type='text' onChange={this.phoneOnChange} value={phone} />
</div>
<div>
<button>Submit</button>
</div>
</form>
</div>
);
}
}
export default ContactUs;
If you run your application again and view the form in the browser, you can fill out each form field and click “Submit” to submit the form. Notice that the page doesn’t reload! And if you look in the developer tool’s console, you’ll see an object containing your contact us information.
Congrats! You’ve completed your first simple React form! In doing so, you used what’s known as “controlled components”.
HTML form elements naturally maintain their own state. For example, an input
element will track the state of the value that’s typed within it (without any help from libraries like React). But a React class component uses this.state
to track its internal state. To keep a component’s state as the “one source of truth”, onChange
event handlers are used on form field elements to update the component’s state when a form element’s state has changed.
This approach of making the component’s state the “one source of truth” is called “controlled components”.
To help understand how this works, here’s an overview of the flow:
<input>
element;<input>
element’s onChange
event is raised;<input>
element’s onChange
event is called;this.setState
method to update the form field’s value in state;render
method is called); and<input>
element is rendered with its value
attribute set to the associated value from this.state
.While all of the above steps might feel like a lot, in reality, the entire process happens very quickly. You can test this yourself by playing around with the ContactUs
component. Typing within each of the form fields feels completely natural.
Adding an onChange
event handler method for each form element can become tedious and quickly bloat the code for your component. Luckily, you can define a single onChange
event handler that’ll work for every form element.
Earlier you learned that a reference to the element that raised the onChange
event is available through the SyntheticEvent
object’s target
property. Using the reference to the form field element, you can retrieve the current value and name of the element like this:
If the form field element’s name
attribute matches the state property name then you can use it to index into the state object to update its value:
This one event handler method can replace all three of the existing onChange
event handler methods: nameOnChange
, emailOnChange
, and phoneOnChange
. To make this work, add name
attributes to each of the form field <input>
elements and update the onChange
attributes to reference the new this.onChange
event handler method:
// ./src/ContactUs.js
import React from 'react';
class ContactUs extends React.Component {
constructor() {
super();
this.state = {
name: '',
email: '',
phone: '',
};
}
onChange = (e) => {
const { name, value } = e.target;
this.setState({ [name]: value });
}
onSubmit = (e) => {
// Prevent the default form behavior
// so the page doesn't reload.
e.preventDefault();
// Retrieve the contact us information from state.
const { name, email, phone } = this.state;
// Create a new object for the contact us information.
const contactUsInformation = {
name,
email,
phone,
submittedOn: new Date(),
};
// For now, just log the contact us information to the console
// though ideally, we'd persist this information to a database
// using a REST API.
console.log(contactUsInformation);
// Reset the form state.
this.setState({
name: '',
email: '',
phone: '',
});
}
render() {
const { name, email, phone } = this.state;
return (
<div>
<h2>Contact Us</h2>
<form onSubmit={this.onSubmit}>
<div>
<label htmlFor='name'>Name:</label>
<input id='name' name='name' type='text' onChange={this.onChange} value={name} />
</div>
<div>
<label htmlFor='email'>Email:</label>
<input id='email' name='email' type='text' onChange={this.onChange} value={email} />
</div>
<div>
<label htmlFor='phone'>Phone:</label>
<input id='phone' name='phone' type='text' onChange={this.onChange} value={phone} />
</div>
<div>
<button>Submit</button>
</div>
</form>
</div>
);
}
}
export default ContactUs;
Don’t forget to remove the nameOnChange
, emailOnChange
, and phoneOnChange
event handler methods from your class component!
In a regular HTML form, the value for a <textarea>
element is defined by its inner content:
The <textarea>
element, in React, uses a value
attribute instead of its inner content to define its value. This allows the <textarea>
element to be handled in the same way as <input>
elements.
To see this in action, add a “Comments” field to the form:
<div>
<label htmlFor='comments'>Comments:</label>
<textarea id='comments' name='comments' onChange={this.onChange} value={comments} />
</div>
To support this new form field, you’ll need to also update the constructor
, onSubmit
, and render
methods:
// ./src/ContactUs.js
import React from 'react';
class ContactUs extends React.Component {
constructor() {
super();
this.state = {
name: '',
email: '',
phone: '',
comments: '',
};
}
onChange = (e) => {
const { name, value } = e.target;
this.setState({ [name]: value });
}
onSubmit = (e) => {
// Prevent the default form behavior
// so the page doesn't reload.
e.preventDefault();
// Retrieve the contact us information from state.
const { name, email, phone, comments } = this.state;
// Create a new object for the contact us information.
const contactUsInformation = {
name,
email,
phone,
comments,
submittedOn: new Date(),
};
// For now, just log the contact us information to the console
// though ideally, we'd persist this information to a database
// using a REST API.
console.log(contactUsInformation);
// Reset the form state.
this.setState({
name: '',
email: '',
phone: '',
comments: '',
});
}
render() {
const { name, email, phone, comments } = this.state;
return (
<div>
<h2>Contact Us</h2>
<form onSubmit={this.onSubmit}>
<div>
<label htmlFor='name'>Name:</label>
<input id='name' name='name' type='text' onChange={this.onChange} value={name} />
</div>
<div>
<label htmlFor='email'>Email:</label>
<input id='email' name='email' type='text' onChange={this.onChange} value={email} />
</div>
<div>
<label htmlFor='phone'>Phone:</label>
<input id='phone' name='phone' type='text' onChange={this.onChange} value={phone} />
</div>
<div>
<label htmlFor='comments'>Comments:</label>
<textarea id='comments' name='comments' onChange={this.onChange} value={comments} />
</div>
<div>
<button>Submit</button>
</div>
</form>
</div>
);
}
}
export default ContactUs;
To maintain symmetry across React form element types, the <select>
element also uses a value
attribute to get and set the element’s selected option. To see this in action, add a <select>
element to the right of the <input>
element for the “Phone” form field, to give the user a way to specify what type of phone number they’re providing:
<div>
<label htmlFor='phone'>Phone:</label>
<input id='phone' name='phone' type='text' onChange={this.onChange} value={phone} />
<select name='phoneType' onChange={this.onChange} value={phoneType}>
<option value=''>Select a phone type...</option>
<option>Home</option>
<option>Work</option>
<option>Mobile</option>
</select>
</div>
In the above <select>
list, the <option>
elements are statically rendered, but it’s also possible to dynamically render them from an array of values. For the array of phone type option values, define a default value for a prop named phoneTypes
:
Then render the <select>
list options using the this.props.phoneTypes
array:
<div>
<label htmlFor='phone'>Phone:</label>
<input id='phone' name='phone' type='text' onChange={this.onChange} value={phone} />
<select name='phoneType' onChange={this.onChange} value={phoneType}>
<option value=''>Select a phone type...</option>
{
this.props.phoneTypes.map(phoneType =>
<option key={phoneType}>{phoneType}</option>
)
}
</select>
</div>
Notice that you can leave the first “Select a phone type…” <option>
element as a static element before rendering the dynamic <option>
elements.
To complete this new field, update the constructor
, onSubmit
, and render
methods just like you did when adding the “Comments” form field:
// ./src/ContactUs.js
import React from 'react';
class ContactUs extends React.Component {
constructor() {
super();
this.state = {
name: '',
email: '',
phone: '',
phoneType: '',
comments: '',
};
}
onChange = (e) => {
const { name, value } = e.target;
this.setState({ [name]: value });
}
onSubmit = (e) => {
// Prevent the default form behavior
// so the page doesn't reload.
e.preventDefault();
// Retrieve the contact us information from state.
const {
name,
email,
phone,
phoneType,
comments,
} = this.state;
// Create a new object for the contact us information.
const contactUsInformation = {
name,
email,
phone,
phoneType,
comments,
submittedOn: new Date(),
};
// For now, just log the contact us information to the console
// though ideally, we'd persist this information to a database
// using a REST API.
console.log(contactUsInformation);
// Reset the form state.
this.setState({
name: '',
email: '',
phone: '',
phoneType: '',
comments: '',
});
}
render() {
const { name, email, phone, phoneType, comments } = this.state;
return (
<div>
<h2>Contact Us</h2>
<form onSubmit={this.onSubmit}>
<div>
<label htmlFor='name'>Name:</label>
<input id='name' name='name' type='text' onChange={this.onChange} value={name} />
</div>
<div>
<label htmlFor='email'>Email:</label>
<input id='email' name='email' type='text' onChange={this.onChange} value={email} />
</div>
<div>
<label htmlFor='phone'>Phone:</label>
<input id='phone' name='phone' type='text' onChange={this.onChange} value={phone} />
<select name='phoneType' onChange={this.onChange} value={phoneType}>
<option value=''>Select a phone type...</option>
{
this.props.phoneTypes.map(phoneType =>
<option key={phoneType}>{phoneType}</option>
)
}
</select>
</div>
<div>
<label htmlFor='comments'>Comments:</label>
<textarea id='comments' name='comments' onChange={this.onChange} value={comments} />
</div>
<div>
<button>Submit</button>
</div>
</form>
</div>
);
}
}
ContactUs.defaultProps = {
phoneTypes: [
'Home',
'Work',
'Mobile',
],
};
export default ContactUs;
One last feature needs to be added before the simple “Contact Us” form is done: form validation. Without validation, a user can submit the form without providing a single bit of data. To implement form validation, you’ll use vanilla JS to validate that the “Name” and “Email” form fields have values before allowing the form to be submitted.
To do that, add a method to your class component named validate
that accepts name
and email
parameters. Use conditional statements to check the truthiness of the name
and email
parameters. If either parameter is false
, add an appropriate validation error message to a validationErrors
array and return the array from the method:
validate(name, email) {
const validationErrors = [];
if (!name) {
validationErrors.push('Please provide a Name');
}
if (!email) {
validationErrors.push('Please provide an Email');
}
return validationErrors;
}
Within the onSubmit
event handler method, call the validate
method and check the length of the returned array to see if there are any validation errors. If there are validation errors, then call the this.setState
method to update the component state, otherwise process the form submission:
// Get validation errors.
const validationErrors = this.validate(name, email);
// If we have validation errors...
if (validationErrors.length > 0) {
// Update the state to display the validation errors.
this.setState({ validationErrors });
} else {
// Process the form submission...
}
In the render
method, use an inline conditional expression with a logical &&
operator to conditionally render an unordered list of validation messages if the validationErrors
array has a length
greater than 0
:
{ validationErrors.length > 0 && (
<div>
The following errors were found:
<ul>
{validationErrors.map(error => <li key={error}>{error}</li>)}
</ul>
</div>
)
}
You’ll also need to update the constructor
method to initialize the validationErrors
state property:
constructor() {
super();
this.state = {
name: '',
email: '',
phone: '',
phoneType: '',
comments: '',
validationErrors: [],
};
}
Putting all of that together, here’s what the updated ContactUs
class component should look like now:
// ./src/ContactUs.js
import React from 'react';
class ContactUs extends React.Component {
constructor() {
super();
this.state = {
name: '',
email: '',
phone: '',
phoneType: '',
comments: '',
validationErrors: [],
};
}
onChange = (e) => {
const { name, value } = e.target;
this.setState({ [name]: value });
}
validate(name, email) {
const validationErrors = [];
if (!name) {
validationErrors.push('Please provide a Name');
}
if (!email) {
validationErrors.push('Please provide an Email');
}
return validationErrors;
}
onSubmit = (e) => {
// Prevent the default form behavior
// so the page doesn't reload.
e.preventDefault();
// Retrieve the contact us information from state.
const {
name,
email,
phone,
phoneType,
comments,
} = this.state;
// Get validation errors.
const validationErrors = this.validate(name, email);
// If we have validation errors...
if (validationErrors.length > 0) {
// Update the state to display the validation errors.
this.setState({ validationErrors });
} else {
// Create a new object for the contact us information.
const contactUsInformation = {
name,
email,
phone,
phoneType,
comments,
submittedOn: new Date(),
};
// For now, just log the contact us information to the console
// though ideally, we'd persist this information to a database
// using a REST API.
console.log(contactUsInformation);
// Reset the form state.
this.setState({
name: '',
email: '',
phone: '',
phoneType: '',
comments: '',
validationErrors: [],
});
}
}
render() {
const {
name,
email,
phone,
phoneType,
comments,
validationErrors,
} = this.state;
return (
<div>
<h2>Contact Us</h2>
{ validationErrors.length > 0 && (
<div>
The following errors were found:
<ul>
{validationErrors.map(error => <li key={error}>{error}</li>)}
</ul>
</div>
)
}
<form onSubmit={this.onSubmit}>
<div>
<label htmlFor='name'>Name:</label>
<input id='name' name='name' type='text' onChange={this.onChange} value={name} />
</div>
<div>
<label htmlFor='email'>Email:</label>
<input id='email' name='email' type='text' onChange={this.onChange} value={email} />
</div>
<div>
<label htmlFor='phone'>Phone:</label>
<input id='phone' name='phone' type='text' onChange={this.onChange} value={phone} />
<select name='phoneType' onChange={this.onChange} value={phoneType}>
<option value=''>Select a phone type...</option>
{
this.props.phoneTypes.map(phoneType =>
<option key={phoneType}>{phoneType}</option>
)
}
</select>
</div>
<div>
<label htmlFor='comments'>Comments:</label>
<textarea id='comments' name='comments' onChange={this.onChange} value={comments} />
</div>
<div>
<button>Submit</button>
</div>
</form>
</div>
);
}
}
ContactUs.defaultProps = {
phoneTypes: [
'Home',
'Work',
'Mobile',
],
};
export default ContactUs;
If you run your application again, view the form in the browser, and attempt to submit the form without providing any form field values, you’ll receive two validation error messages:
The following errors were found:
* Please provide a Name
* Please provide an Email
Overall, this approach to validating the form is relatively simple. You could validate the data as it changes so that the user would receive feedback sooner (i.e. not having to wait to submit the form to see the validation error messages). Sometimes it’s helpful to receive feedback in real time, but sometimes it can be annoying to users. Consider each situation and use an approach that feels appropriate for your users.
You can also use a validation library like Validator.js to add more sophisticated form validations.
First, install the validator
npm package:
Then import the email validator into the ./src/ContactUs.js
module:
Now you can use the isEmail
validator function to check if the provided email
value is in fact a valid email address:
validate(name, email) {
const validationErrors = [];
if (!name) {
validationErrors.push('Please provide a Name');
}
if (!email) {
validationErrors.push('Please provide an Email');
} else if (!isEmail(email)) {
validationErrors.push('Please provide a valid Email');
}
return validationErrors;
}
If you run your application again, view the form in the browser, and attempt to submit the form with an invalid email address, you’ll receive the following validation error message:
The following errors were found:
* Please provide a valid Email
As a reminder, client-side validation like the validations in the ContactUs
class component, are optional to implement; server-side validation is not optional. This is because client side validations can be disabled or manipulated by savvy users.
Sometimes the “best” approach is to skip implementing validations on the client-side and rely completely on the server-side validation. Using this approach, you’d simply call the API when the form is submitted and if the request returns a 400 BAD REQUEST
response, you’d display the validation error messages returned from the server.
If you do decide to implement client-side validations, do it with the end goal of improving your application’s overall user experience, not as your only means of validating user provided data.
In this article, you learned how to:
onChange
events for multiple <input>
elements;<textarea>
element to a form;<select>
element to a form; andWhen creating a React class component, you define its props and state, how it’ll handle user generated events, and how it’ll render, but you don’t directly control when those things occur. And while you can add your component to another component’s render
method (making it a child of that component), you don’t control when your component will be loaded into (or unloaded from) the component tree. You also don’t control when your component will be updated and re-rendered.
The lifecycle of a component is simply a way of describing the key moments in the lifetime of a component: when it’s loading (i.e. mounting), updating, and unloading (i.e. unmounting).
When you finish this article, you should be able to:
componentDidMount
, componentDidUpdate
, and componentWillUnmount
; andcomponentDidMount
component lifecycle method to fetch data from an API.Each class component has several lifecycle methods that you can add to run code at specific points in a component’s lifetime:
componentDidMount
- This method is called after your component has been added to the component tree.componentDidUpdate
- This method is called after your component has been updated.componentWillUnmount
- This method is called just before your component is removed from the component tree.Let’s take a closer look the process that occurs when a component is mounting, updating, and unmounting.
When a class component is being added to the component tree, the following process occurs:
constructor
method is called;render
method is called;componentDidMount
lifecycle method is called.A component will update if it receives new props or if the setState
method is called.
When a component receives new props, the following process occurs:
render
method is called;componentDidUpdate
lifecycle method is called.When a the setState
method is called, the following process occurs:
render
method is called;componentDidUpdate
lifecycle method is called.Just before a class component is removed from the component tree, the componentWillUnmount
lifecycle method is called.
To see a visual depiction of the above processes, check out this interactive lifecycle diagram.
In earlier versions of React, there were additional lifecycle methods that you could use. These methods are now deprecated and are marked as “unsafe” to use (because they’ll eventually be removed from React’s API).
The legacy lifecycle methods are:
Sometimes you’ll encounter these lifecycle methods in older articles and code examples when researching React online. To learn more about these lifecycle methods, see the official React documentation.
To see the componentDidMount
, componentDidUpdate
, and componentWillUnmount
lifecycle methods in action, you can create a couple of simple React class components.
For the first class component, you’ll define the componentDidMount
, componentDidUpdate
, and componentWillUnmount
lifecycle methods and render an <h2>
element using the this.props.text
prop for its content:
// ./src/LifecycleMethods.js
import React from 'react';
class LifecycleMethods extends React.Component {
componentDidMount() {
debugger;
}
componentDidUpdate() {
debugger;
}
componentWillUnmount() {
debugger;
}
render() {
return (
<h2>{this.props.text}</h2>
);
}
}
export default LifecycleMethods;
The debugger
statements will cause the browser to break within each of the lifecycle methods when they’re invoked.
For the second class component, you’ll create a component that’ll use the LifecycleMethods
component:
// ./src/Demo.js
import React from 'react';
import LifecycleMethods from './LifecycleMethods';
class Demo extends React.Component {
constructor() {
super();
this.state = {
displayComponent: false,
componentText: new Date().toLocaleString(),
};
}
loadComponent = () => {
this.setState({ displayComponent: true });
}
unloadComponent = () => {
this.setState({ displayComponent: false });
}
updateComponent = () => {
this.setState({
componentText: new Date().toLocaleString(),
});
}
render() {
return this.state.displayComponent ?
(
<div>
<div>
<button type='button' onClick={this.unloadComponent}>Unload Component</button>
<button type='button' onClick={this.updateComponent}>Update Component</button>
</div>
<LifecycleMethods text={this.state.componentText} />
</div>
) : (
<div>
<button type='button' onClick={this.loadComponent}>Load Component</button>
</div>
);
}
}
export default Demo;
Notice that the component renders buttons to control when the LifecycleMethods
component is loaded, updated, and unloaded. Having these buttons will allow you to interactive trigger each of the lifecycle methods defined within the LifecycleMethods
component.
If you’re following along, be sure to update your React application’s entry point to render the Demo
component:
// ./src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import Demo from './Demo';
ReactDOM.render(
<React.StrictMode>
<Demo />
</React.StrictMode>,
document.getElementById('root')
);
Then run your application (npm start
) and open the developer tools console in your browser. Here’s the application just after clicking the “Load Component” button. Notice that execution is paused within the LifecycleMethods
component’s componentDidMount
lifecycle method!
A common use case for the componentDidMount
lifecycle method, is to fetch data from an API after a component has been mounted to the DOM. Here’s an example of a class component that uses the componentDidMount
lifecycle method to retrieve the public repositories for the provided GitHub username and render the repositories as an unordered list of links:
// ./src/FetchingData.js
import React from 'react';
class FetchingData extends React.Component {
constructor() {
super();
this.state = {
repositories: [],
};
}
componentDidMount() {
const url = `https://api.github.com/users/${this.props.gitHubUsername}/repos`;
fetch(url)
.then((response) => response.json())
.then((data) => this.setState({ repositories: data }));
}
render() {
const { repositories } = this.state;
if (repositories.length === 0) {
return (
<div>Fetching data...</div>
);
} else {
return (
<div>
<h2>GitHub Repositories for {this.props.gitHubUsername}</h2>
<ul>
{
repositories.map((repo) => (
<li key={repo.id}>
<a href={repo.html_url}>{repo.name}</a>
</li>
))
}
</ul>
</div>
);
}
}
}
export default FetchingData;
In the above example, the FetchingData
component initially renders <div>Fetching data...</div>
. Once the component is mounted to the DOM, the componentDidMount
lifecycle method is called, which in turn uses the Fetch API to retrieve the public repositories for the provided GitHub username. When the fetch HTTP request completes and the JSON response is parsed, the this.setState
method is called to update the this.state.repositories
property with the newly acquired data. Updating the state causes React to re-render the component which then displays an unordered list of links.
If you’re following along, be sure to update your React application’s entry point to render the FetchingData
component and to set the gitHubUsername
prop on the component to a valid GitHub username:
import React from 'react';
import ReactDOM from 'react-dom';
import FetchingData from './FetchingData';
ReactDOM.render(
<React.StrictMode>
<FetchingData gitHubUsername='appacademy' />
</React.StrictMode>,
document.getElementById('root')
);
Here’s the component displaying the public repositories for the appacademy
GitHub username:
In this article, you learned how to:
componentDidMount
, componentDidUpdate
, and componentWillUnmount
; andcomponentDidMount
component lifecycle method to fetch data from an API.In addition to the componentDidMount
, componentDidUpdate
, and componentWillUnmount
lifecycle methods, there are additional rarely used lifecycle methods. To learn more about these additional lifecycle methods, see the official React documentation.
Now that you’ve learned the basics of React, the official React documentation is a great resource to use to reinforce your newly acquired skills.
The Main Concepts section of the official documentation is a great series of articles that cover the basics of React. If you’re in the mood for a step-by-step tutorial, be sure to check out the Intro to React tutorial.
Once you’re feeling confident with the basics of React development, you can extend your knowledge by reviewing the following topics listed under the “Advanced Guides” section of the official documentation:
The rest of the topics listed under the Advanced Guides section can safely be ignored for now. Just remember that when you encounter an issue or need to solve a problem that you haven’t solved yet, the official documentation is a great resource!
You’re going to build a simple calculator app. Our app takes in two numbers and shows the result of a simple operation on the numbers when you click the operation button. Take a look at the Live Demo to see the app in action. Assume that only numbers will be entered.
Begin by using the create-react-app package. You’ll use the command below to create a React application.
Take note that using the create-react-app
command initializes your project as a Git repository. If you use the ls -a
to view the hidden files in your project, you’ll see the .git
file.
You’ll also see that your package.json
file includes four auto-generated scripts: start
, build
, test
, and eject
.
Today, you’ll be focusing on writing code in the project’s src
directory. But before you begin, let’s take a moment to walk through how your view is rendered.
Start your development server with the npm start
command and your browser should open http://localhost:3000/
to render a view. This view is connected to your entry file (./src/App.js
). Open your developer tools and view your HTML elements in the Elements tab. If you open up your App.js
file, you’ll see that the JSX in the file is similar to the HTML in your developer tools.
Although your App.js
file is generated as a JavaScript file with a .js
extension, JSX is used to produce and render the React elements. As a reminder, JSX is a syntax extension that ultimately get converted to vanilla JavaScript. It is not HTML although the syntax is similar. An example of a difference is the use of className
instead of the HTML class
attribute. You’ll learn more about how Babel is used to transpile JSX into JavaScript.
For now, let’s refactor and clean up your App
component by replacing its content:
// App.js
import React from 'react';
function App() {
return (
<div className="App">
<h1>Calculator</h1>
</div>
);
}
export default App;
Since your React app is rendering with JavaScript, you can return your App
component with an arrow function. Replace your App.js
file with the code below and see how the same view is rendered in http://localhost:3000/
:
// App.js
import React from 'react';
const App = () => {
return (
<div className="App">
<h1>Calculator</h1>
</div>
);
}
export default App;
In addition, you can use parentheses to implicitly return the App
component:
// App.js
import React from 'react';
const App = () => (
<div className="App">
<h1>Calculator</h1>
</div>
);
export default App;
But how does the JSX in App.js
get rendered? Use cmd + shift + f
to find where the <App />
is rendered in your application. You should see the index.js
entry file. At the top of the file, you’ll see that the App
component has been imported. Since your App.js
file is returning JSX, you can render the JSX as a <App />
component by using the ReactDOM.render() method within your entry index.js
file. The role of the index.js
entry file is to render your React components.
Notice that the ReactDOM.render() method’s second argument is finding an HTML element with the id
of root
. Take a moment to use cmd + shift + f
to find id="root"
. You should now find a <div>
element with an id
of root
in the index.html
file. The ReactDOM.render() method is replacing the <div>
element with the JSX.
Congratulations! You now have a basic React application set up with a component that you understand how to render.
Calculator
componentNow create a file called Calculator.js
within your src
directory. Start with the code skeleton below:
import React from "react";
class Calculator extends React.Component {
constructor(props) {
super(props);
// TODO: Initialize state
}
render() {
return (
<div>
<h1>Time for math!</h1>
</div>
);
}
}
export default Calculator;
In your App.js
file, import the Calculator
component and set it to render underneath the <h1>
element. Make sure “Calculator” still shows up in the browser, this time with “Time for math!” from your Calculator
component.
Now let’s initialize the state
of your Calculator
component! The state of your component is just a JavaScript object. For the calculator, it will contain three keys: the result and two numbers from user input.
Within the constructor()
method of your Calculator
component, define this.state
with default values for the result and two numbers. The result
should have a default value of 0
. You actually want the two numbers to start out blank, so give num1
and num2
a default value of an empty string:
The first thing you want to render is your result
. Notice how your Calculator
and App
components are rendering JSX elements in different ways. Your Calculator
component is a class component, so it needs to use the render()
method to return JSX, while your App
component is a function component so it can directly return JSX. You want to interpolate the result, which is stored in the component’s state
, into the JSX. It’ll look something like this:
Let’s make the input fields. You want the state
to receive the new value of the input field every time something is typed in. You can do this by passing an onChange
event handler as a prop to the input field. Whenever the input field’s value changes (via the user), the input will run its onChange
prop, which should be a callback. Let’s create a callback as a method inside your component. Begin by console logging the change event that is passed into the callback.
Add an <input>
element underneath your rendered result
. Assign the onChange
prop to a handleFirstNum()
callback like so:
Try typing in your “First number” input field and seeing what is logged in your developer tools console from the change event. As a reminder, event objects from your event listeners have target and currentTarget elements. In this case, both the event.target
and event.currentTarget
refer to the <input>
element.
Update your handleFirstNum
method to use the parsed value
of your event.target
to set the num1
state. As a reminder, parsing non-numeric strings results in a NaN
(“Not a Number”) output. Also make note that you need to use this.setState() in order to set a component’s state and re-render the component with the updated state.
You also want your input fields to always reflect the current version of the state and properly update when you trigger a re-render by changing the state, so make sure to include value={this.state.num1}
in the input tag.
That’s one of the inputs! Create a second <input>
element and a handleSecondNum()
callback. It should look very similar.
Time to write the operations. Each one of these is a button, with an onClick
callback set that carries out the operation and sets the state of the result to the answer. For example, you can create a “+” button with an onClick
listener to invoke an add()
method with num1
and num2
to update the result
state.
The current values for num1
and num2
should be properly updated and stored within the state of your component. Create four methods to handle adding, subtracting, multiplying, and dividing. Remember to use setState()
to set this.state.result
to the correct result.
It’d also be nice to be able to clear out the input fields. Make a button that resets the state to its initial values. You can add an onClick
listener to this button to invoke a clearInput
method to reset the state, and therefore clear each input field’s value
.
This is part of why it’s important to set a value
on the input fields. By having the value depend on the state, you ensure that the value will be re-rendered, and therefore be properly cleared when you set the state of num1
and num2
back to empty strings.
You’re probably using the values stored in your state a few times in your render
method. Let’s DRY it up a little. Destructure the properties stored in your state in your render
method to be able to refer to them by separate variables. Remember that any JavaScript you do should happen before the return
statement!
Congratulations! You’ve created your first React application!
In this small project, you created your first React class-based component and used it to store state and handle events.
To practice creating React components, you are going to build four simple widgets. You’ll be building a clock widget, an interactive folder widget, a weather widget, and a simple search input component.
By the end of this project, you will:
Generate a new React application called “Widgets” with create-react-app by running npx create-react-app widgets --template @appacademy/simple
. Note how you are using a custom template to generate your React application.
Once your project has been initialized, in the index.js
file you’ll see that ReactDOM
is rendering a <React.StrictMode>
component. StrictMode simply means that additional checks and warnings will be made in development mode. It’s a helpful tool that highlights potential problems.
// index.js
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
Let’s rename the rendered App
component to be a component named Root
. Make sure to update where you have imported App
and to update the App.js
file name to Root.js
.
The Root
component should be a function component because it won’t use internal state or any lifecycle methods. For now, have your Root
component return an empty <div>
. You will fill this in with your widget components as you create them. At this point, your Root.js
file should look something like this:
The clock component should display the current date and time, updating every second. Start by creating a new file Clock.js
in your src
folder importing React
into the file. Define your Clock
class to extend from React.Component
and remember to export the class. You will import your Clock
component into your Root.js
file and incorporate it into the return value of your Root
. This is the pattern you will follow for all the widgets.
Now it’s time to create a render method! Have your clock render a “Clock” title in an <h1>
element and check that this renders correctly on the page.
In the constructor, set the initial state for the time of your clock using new Date()
like so:
Write a method, tick
that uses setState
to update the time
to a new Date()
. Remember to define this method using an arrow function or else you’d need to bind the function in the constructor.
Now you can define a componentDidMount() method to initialize the ticking of your clock. As a reminder, the componentDidMount()
method is one of the lifecycle methods. When a component is mounted, the render()
method will first return the component’s JSX elements. Then componentDidMount()
will be called. You can often house your logic to fetch information that updates state in this lifecycle method.
For the componentDidMount()
method in your Clock
component, you’ll use JavaScript’s setInterval()
method to call your this.tick()
method every second.
You’ll also want to store that interval as a property of the Clock
class that you can cancel with clearInterval()
in componentWillUnmount(), which gets called just before the component is removed. Don’t store this in the component’s state
since it doesn’t affect the UI. Instead, just store it directly on this
, like so:
In your render method, display the current hours
, minutes
, and seconds
. Check out all of the Date object methods you can use to display the date and time in a human-readable string.
You’ll notice that you have an index.css
file already imported into your entry index.js
file. Create and include a reset.css
file before the line to import your index.css
file.
Feel free to use the following CSS reset file template:
/* reset.css */
a, article, body, button, div, fieldset, footer, form, h1, h2, header, html, i, img, input, label, li, main, nav, p, section, small, span, strong, textarea, time, ul {
background: transparent;
border: 0;
box-sizing: inherit;
color: inherit;
font: inherit;
margin: 0;
outline: 0;
padding: 0;
text-align: inherit;
text-decoration: inherit;
vertical-align: inherit;
}
ul {
list-style: none;
}
img {
display: block;
height: auto;
width: 100%;
}
button, input[type="email"], input[type="password"], input[type="submit"], input[type="text"], textarea {
/*
Get rid of native styling. Read more here:
http://css-tricks.com/almanac/properties/a/appearance/
*/
-moz-appearance: none;
-webkit-appearance: none;
appearance: none;
}
button, input[type="submit"] {
cursor: pointer;
}
Now go to Google Fonts and select a nice font for your clock. In the public/index.html
file, update your page to have a title
of “Widgets”. Now take the font embed code and paste it into the <head>
of your page.
Your index.html
file should look something like this:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link href="https://fonts.googleapis.com/css2?family=Orbitron" rel="stylesheet">
<title>Widgets</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
To use the font, set the font-family
of your element to the font name in your index.css
file.
Set the time and date headers to be on one side and the actual time and date to the other. Refer to the live demo to see what your end goal is. You can achieve this easily with a flexbox. Take a look at the justify-content
property. Which one do you want to use? Try all of them to understand what they do.
Add a background. Use the background
or background-color
property to change the background. Feel free to do this for every widget.
You should now have a clock that displays the current time and date. You used setInterval()
to make sure that the clock updates every second, and clearInterval()
to clear the timer that setInterval()
set. Once you have sufficiently styled your clock, move on to the next widget.
You’re going to add a folder widget that the user can interact with. The folder tabs should each be labeled with their own title. The selected tab should be in a bold font. Below the tab, display the contents of the selected tab. The folder content should update when the user selects different tabs.
Make a Folder
component. Root
should pass the Folder
component a folders
prop. The prop should be an array of JavaScript objects that each have title
and content
as properties:
Folder component
Folders prop
const folders = [
{title: 'one', content: 'I am the first'},
{title: 'two', content: 'Second folder here'},
{title: 'three', content: 'Third folder here'}
];
Keep track of the selected tab’s index in your Folder
component’s state. Set the Folder
component’s default currentTab
state to zero.
In the render method, return an <h1>
element with the title of “Folder”. You’ll begin by rendering one folder’s content, using the currentTab
state to select which folder content to render.
Render a <div>
element with two child elements: a header to render folder titles (you’ll make a <Header>
subcomponent) and a <div>
element to render the selected tab’s content. Define a folder
variable by indexing into your folders
prop with your currentTab
state. This way you can reference your selected folder’s content with clean code!
At this point, your component’s render()
method should look something like this:
render() {
const folder = this.props.folders[this.state.currentTab];
return (
<div>
<h1>Folder</h1>
<div className='tabs'>
{/* TODO: render folder titles */}
<div className='tab-content'>
{folder.content}
</div>
</div>
</div>
);
}
Take a moment to observe the syntax for making a comment inside of JSX. If you use VS Code’s keyboard shortcut (cmd + /
) to comment, you will not make a valid comment. You need to use block comment syntax wrapped in curly braces in order to write comments in JSX!
Remember that JSX interpolation is just syntactic sugar and that it only supports expressions, so you also can’t use if
/else
inside { }
. However, [ternary conditionals] are valid inside JSX interpolation.
Now create a selectTab()
method that takes in a selected folder index. You’ll use this method to update the currentTab
state with the input index. For now, have the method console log the index input.
Let’s move forward with rendering the folder titles!
Let’s create a Headers
subcomponent to render your folder titles! Within your Folder.js
file, create a subcomponent above your Folder
class. This subcomponent will take care of rendering an unordered list of list items containing clickable tabs.
Plan what information you want to pass as props from your Folder
component into your Headers
. You’ll want to render each tab’s title, so you’ll probably want to thread a titles
prop from your Folder
component. Map over your array of folders
to define a titles
array of folder titles. Now thread your titles
array as a prop to the Headers
subcomponent. As a reminder, “threading props” simply refers to passing props from one component to another.
You also want to pass the currentTab
state so that the Headers
component can know which tab to render with different CSS as selected or active.
Lastly, you’ll want your Headers
component to be able to use the selectTab()
method you have defined in order to update the tab’s currentTab
state.
Your Folder
component should render the Headers
subcomponent below:
Now let’s dive into what your Headers
component should render. Begin by returning an unordered list:
Instead of taking in a props
argument and referring to all your props like props.folders
or props.currentTab
, you can destructure the props you have received like so:
const Headers = ({ titles, currentTab, selectTab }) => {
return (
<ul className='tab-header'>
</ul>
);
}
Now map your folder titles
to list item elements that render each folder’s title
. You’ll need to pass a unique key
property to each <li>
or React will grumble to all your console-reading users about its unfair working conditions. “How is one supposed to efficiently diff the DOM when one doesn’t even know which list items match up with which!?”
const Headers = ({ titles, currentTab, selectTab }) => {
return (
<ul className='tab-header'>
{titles.map((title, idx) => {
return (
<li key={idx}>
{title}
</li>
);
})}
</ul>
);
}
To clean up your return, you can extract your list elements as a tabs
variable:
const Headers = ({ titles, currentTab, selectTab }) => {
const tabs = titles.map((title, idx) => {
return (
<li key={idx}>
{title}
</li>
);
});
return (
<ul className='tab-header'>
{tabs}
</ul>
);
};
Now add an onClick
handler to each list item to update the currentTab
state in the Folder
component. You’ll also want to set the id
of the <li>
element to each title’s index. You can then reference the index through e.target.id
to use in the selectTab()
function.
You might ask why not just preset an argument with an arrow function callback directly in the onClick
. It is actually bad practice to do so! Feel free to read more here. In this case, it’s better to handle the event and invoke the selectTab()
function within the click handler.
/* BAD PRACTICE */
return (
<li key={idx} id={idx} onClick={() => selectTab(idx)}>
{title}
</li>
);
/* GOOD PRACTICE */
return (
<li key={idx} id={idx} onClick={handleClick}>
{title}
</li>
);
Define a handleClick()
function in your Headers
component. Reference the folder’s index through e.target.id
and parse the id
into an integer to invoke the selectTab()
function:
At this point, test your click handler. Click your folder titles and open your developer tools console. You should see the logging of clicked folder indices. After you have confirmed your click handler is working, update your selectTab()
function to set the currentTab
state using its input.
Before you move forward to focusing on a specific tab, add some styling to make your Folder
widget look like folders with tabs! Add a border around each tab and use border-radius
to add nicely curved corners to the top of your tabs.
Use a flexbox to ensure that the tabs all take up the same amount of space. Add display: flex
to your CSS for your folder tabs. Center the folder content, both horizontally and vertically.
Add a hover effect to change the background color of the tab that’s being moused over. Change the cursor
to be a pointer
when you’re mousing over the tabs to make it clear that the tabs are interactive.
Now let’s be able to focus on a specific tab! At this point, you should have a widget that displays the content of all your folder tabs.
In your Headers
subcomponent, you’ll want to assign an active
class to your selected tab. The selected tab’s label should be bold and the folder content should update when a different tab is selected. Within the mapping of your header titles
, you can compare the idx
of each title to the folder’s currentTab
state to decide whether a list item should have the CSS class name of active
.
For example, you can use a ternary operator to assign a headerClass
variable like this:
Feel free to restyle your Folder
component by adding the CSS below into your index.css
file. Play around with changing the .tab-header > li.active
class styling to manipulate the styling of your selected tab!
/* Folder */
.tab-header {
margin: 0 20px;
display: flex;
justify-content: space-between;
}
.tab-header > li {
width: 33%;
border-top: 2px solid black;
border-left: 1px solid black;
border-right: 1px solid black;
border-bottom: 2px solid black;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
padding: 5px;
text-align: center;
cursor: pointer;
background-color: lightpink;
}
.tab-header > li:first-child {
border-left: 2px solid black;
}
.tab-header > li:last-child {
border-right: 2px solid black;
}
.tab-header > li:hover {
background-color: lightblue;
color: white;
}
.tab-header > li.active {
color: white;
font-weight: bold;
background-color: lightblue;
border-bottom: 0px;
}
.tabs {
width: 240px;
}
.tab-content {
font-weight: bold;
color: white;
height: 192px;
margin: 0 20px;
border-left: 2px solid black;
border-bottom: 2px solid black;
border-right: 2px solid black;
display: flex;
align-items: center;
justify-content: center;
background-color: lightblue;
}
In this phase, you will create a weather widget to display the current weather based on the user’s location. You will be using the navigator.geolocation
API to get the user’s current location, and the OpenWeatherMap API to get the current weather.
Make a Weather
component, which again, will be incorporated into your Root
component. Now set your component’s default state with a null
weather object in your constructor, like so:
Review the OpenWeatherMap API documentation. You’ll use this API to get the weather based on your current location (it is recommended to fetch the weather by geographic coordinates). Upon a successful fetch, you’ll update your component’s state.
In order to get the API to accept your HTTP requests, you’ll need an API key. Read up on how to use the API key and sign up for one here. After signing up, click on the API keys tab to get your key. You may need to open their welcome email before the API key will work.
In the real world, you should be very careful about placing API keys in frontend JavaScript or anywhere else they are publicly available and can be scraped (this includes public Git repositories). Stolen keys can cost you. You have been warned.
Now let’s get your current location! When the component mounts, call navigator.geolocation.getCurrentPosition()
to get it. Read through the navigator documentation to figure out how to use this method properly. (Make sure you have location services enabled in your browser, or this won’t work.)
From reading the documentation, you know that there are two methods to access a browser’s location data: - getCurrentPosition()
- watchPosition()
Let’s look at the documentation for the getCurrentPosition()
method to find out more about its expected parameters. You should see a Syntax portion on the documentation with the method breakdown below:
You’ll also see that there is a Parameters section below that outlines a mandatory success
callback function, an optional error
callback, and an optional options
object. In documentation, square brackets around a parameter indicates that it is an optional parameter.
Now let’s test the getCurrentPosition()
method in your developer tools console. Console log a result as the method’s success
callback like so:
You should have received a request to share your location with the browser! Upon allowing the browser to know your location, you should console log a GeolocationPosition
object when invoking the method again in the console:
Begin by invoking the getCurrentPosition()
method in your Weather
component’s componentDidMount()
method. Upon successfully retrieving your browser’s location, you’ll invoke a success callback to query the weather API.
Let’s create your success callback! Create a pollWeather()
method to take in your received location
result from navigator.geolocation.getCurrentPosition()
. You’ll use the latitude
and longitude
of your location to make a fetch call to the weather API. Think of how to extract the latitude
and longitude
properties from your GeolocationPosition
object. Also think of how you might structure your fetch URL to include the query parameter for your geographic coordinates.
Navigate to the By geographic coordinates
section in the OpenWeatherMap API documentation. You’ll see an example of an API query string using latitude and longitude coordinates (api.openweathermap.org/data/2.5/weather?lat=35&lon=139
). You’ll also see an example JSON response below.
You can define a toQueryString()
helper method to format your query parameters into a fetch call URL. To think of scaling your “Widgets” project, you can move this helper function into a utils.js
file so that it can be used for other APIs you might incorporate! Have the function take in a params
object. You’ll then iterate through the object to sanitize each query value with encodeURIComponent(). You can then return a query string like lat=35&lon=139
to build an example API query string above.
In your pollWeather()
method, use the Fetch API to make a fetch call to the OpenWeatherMap API. Remember to parse your response as JSON before updating the weather
state. Upon a successful fetch, update your component’s weather
state with the weather
property of your JSON response! Use your component’s state to render the current city and temperature on the page.
By default, the OpenWeatherMap API will return the temperature in Standard units (Kelvin). Convert to Fahrenheit OR peruse the API docs for a way to request the weather in Imperial units (Fahrenheit)! Give the weather box a nice border and make sure the elements inside are spaced evenly.
Great work! Now you have three widgets. One that displays the time, another that allows you navigate folder tabs, and another that displays the weather. You used the navigator.geolocation
API to get your current location, which you then passed to your fetch request to get the weather from the OpenWeatherMap API.
Make an Autocomplete
component that filters a list of names by the user’s input. Match only names that start with the search input. When a user clicks on a name, the input field should autocomplete to that name. Create a new file Auto.js
and define your Autocomplete
class there. Incorporate it into Root
.
Because your autocomplete widget should be reusable, you shouldn’t hard code a list of names into the component. Instead of hard coding the names, set up your Autocomplete
component to accept names
as a prop. Then set the component’s initial state for inputVal
as an empty string.
Build your widget in the render
method. It should contain an input field and an unordered list. Render an <li>
inside the <ul>
for every name that begins with the value in the input box. Remember to pass your unique key
property to each <li>
!
When a user types something into the input, use an onChange
event handler to update the widget’s state. Create a handleInput()
event handler method to update the state of inputVal
with the typed input value.
Also add an onClick
handler to the unordered list. The role of this click handler is to update the widget’s search string (the inputVal
state) upon a user’s click of the <li>
element you’ve created for each name. You will need to turn your <input>
into a controlled component for this to work. Would you access the event’s currentTarget or target? Remember to use setState()
to update the widget’s search string.
Now you’ll want to find the names that match your user’s search input. Define a matches()
method to generate an array of name matches
based on the inputVal
state. Since you’re taking in user input, think of how you could use regular expressions to match the character combinations between your user’s input string and the list of searchable names. If the input is empty, return the original, full list of names so that your user can see all the searchable names!
Now let’s generate the name matches! Iterate through each name. You’ll use the length of inputVal
to slice a segment of each name. Compare the name segment with the input value. Take into consideration that some users might type “barney” instead of searching for “Barney”.
For example, compare the name segment to the input value in order to match a search input of “bar” to the “bar” segment of “Barney”. Then you could add the name, “Barney”, to your matches
array. On the next iteration, the “bar” input would also match to “Barbara” so that you could add “Barbara” to the matches
array.
If you have no matches, you can add a “No matches” string to your matches
array so that when matches
is returned and rendered, your user will be notified upon searching for a name without matches.
Give your component a border and make sure all the <li>
elements are nicely padded inside the box. Change the cursor
property to display a pointer when hovering over one of the <li>
elements. Center all your widgets using flexboxes. Which justify-content
property would you use for this?
Great job! The autocomplete widget uses an event handler to update the state of the component when letters are typed into the input field. Once the autocomplete widget is sufficiently styled, move on to the bonus phase to make your widgets even better.
Right now, in the autocomplete widget, the matched names instantly appear on the screen and the filtered names instantly disappear. This is abrupt and ugly. You want the names to fade out or in when they are entering or leaving the page. How can you achieve that with React? With React Transition Group!
First you need to import the CSSTransition
module into your project. In the console, run npm install react-transition-group@^4.0.0 --save
.
Then you need to import the module in the file. At the top of Auto.js
, write import CSSTransition from 'react-transition-group';
.
In your render
method, you will need to wrap the group of elements that will be entering and leaving the screen with the <TransitionGroup>
element. In the case of the autocomplete widget, wrap the results rendered as <li>
, within the <ul>
. You are not wrapping each individual <li>
, but rather the entire group.
Now you’ll need to wrap each individual <li>
with a <CSSTransition>
element. Move the list item’s key
to the <CSSTransition>
element.
<CSSTransition>
has three necessary attributes. Read what they are below and make sure to include them:
classNames
: This is the name that’s used to create all of the transition classes. For now, let’s set this to "result"
, but you can pick any name you like.
timeout
: Specifies how long (in ms) the transition should last. This prop takes in an object with two keys (timeout={{ exit: exitNumber, enter: enterNumber }}
). * enter
: Length of the transition when the element enters. This needs to be a number, so you’ll have to interpolate the JavaScript number, otherwise it’ll be read as a string. (i.e {500}
instead of 500
). * exit
: Same as above, except for when an element is leaving the page.
Finally the CSS. Create a new CSS file and paste in the code below. Be sure to import your new CSS file into your entry index.js
file so the transitions are applied.
The CSS below assumes you’ve given the classNames
attribute to result
. If you gave it a different name, just replace every result
with the name you gave.
/* AutoComplete */
.result-enter {
opacity: 0.01;
transform: translateY(500%);
}
.result-enter.result-enter-active {
opacity: 1;
transform: translateY(0);
transition: opacity 500ms, transform 500ms;
}
.result-exit {
opacity: 1;
transform: translateY(0);
}
.result-exit.result-exit-active {
opacity: 0.01;
transform: translateY(500%);
transition: opacity 500ms, transform 500ms;
}
Go play with the widget! You’ll notice that when names appear, they fade in from the bottom. When they leave, they fade out and fall to the bottom. Let’s break down the CSS file:
.result-enter
: Specifies the initial state of an element that is entering the page. Since I want the names to start invisible and at the bottom, I’ve given it the opacity
and transform
properties the appropriate values.
.result-enter.result-enter-active
: Specifies the final state of an element that has entered the screen. Looking at the CSS, you can see that I expect the element to be completely opaque and in it’s original y-position when it is done entering. This is where you also specify the transition
property.
.result-exit
: Specifies the initial state of an element that is leaving the page. In almost all cases, the values of this class with match the values in the result-enter.result-enter-active
class.
.result-exit.result-exit-active
: Specifies the final state of an element that has left the screen. This is where you also specify the transition
property.
Play around with the CSS file. What kind of interesting transitions can you create?
Check out your new transition in the browser. Open up your developer tools and type something in the “Autocomplete” search input. Your transitions are working, but wait - you have a warning in the console!
Warning: findDOMNode is deprecated in StrictMode. findDOMNode was passed an instance of CSSTransitionGroupChild which is inside StrictMode. Instead, add a ref directly to the element you want to reference. Learn more about using refs safely here: https://fb.me/react-strict-mode-find-node
This is an example of how StrictMode is a helpful tool that highlights potential problems. In this case, StrictMode
is giving you helpful information about the deprecation of findDOMNode
, which is used under the hood. You are also given a clickable link to the official React documentation!
According to the documentation, findDOMNode
is used “to search the tree for a DOM node given a class instance.” Now is your chance to practice going through the official React documentation and learning from reading a merged PR in the official react-transition-group repository! Take a moment to read through the merged PR to see real-life discussion about implementing the nodeRef
feature as an alternative to having React use findDOMNode
under the hood.
In your constructor method, create a ref with React.createRef()
and use the ref to assign a nodeRef
prop to the <CSSTransition>
that wraps your result items. Doing this will allow React to reference the <CSSTransition>
component, without using the deprecated findDOMNode
method to search through the tree for the component. Since React is no longer using findDOMNode
under the hood, using a nodeRef
will remove the warning in the developer tools console.
Congratulations! You have just read through official documentation. In the future, you may contribute to an open-source or community managed project, just like how the use for the merged PR did! Don’t be discouraged by reading live discussion in GitHub issues and pull requests. You’ll continue building your foundation of React knowledge and before you know it, you might even be contributing to projects yourself!