WEEK-14 DAY-3
React With State




React Class Components Learning Objectives

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:


Simple React Project

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

Using a custom template

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:

npx create-react-app my-app --template @appacademy/simple

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 like deleting files…

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:

npx create-react-app simple-project

Then open the simple-project folder in your code editor.

Simplifying the public folder

In the public folder, you’re only going to keep the index.html file, so remove all of the following:

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:

Simplifying the src folder

In the src folder, you’re going to keep the following files:

And remove all of the following:

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.css */

/* TODO Add site wide styles */
// ./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

What you learned

In this article, you learned how to:


React Class Components

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:

Hello class components!

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

Setting and accessing props

Notice from this example of using the Message class component that props are set on class components just like you do with function components:

<Message text='Hello world!' />

To access props within a class component, use the this.props property:

class Message extends React.Component {
  render() {
    return (
      <div>{this.props.text}</div>
    );
  }
}

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.

Legacy class component syntax

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.

Stateful components

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.

What is state?

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.

When to use state

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.

Initializing state

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.

Using the React Developer Tools to view a component’s state

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.

init state
init state

Updating state

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 of onclick) and pass a reference to the event handler method instead of calling it (i.e. this.changeQuote instead of this.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 the changeQuote 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 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;

Don’t modify state directly

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:

Do not mutate state directly. Use setState()  react/no-direct-mutation-state

Properly updating state from the previous state

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!

Providing default values for props

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

What you learned

In this article, you learned how to:


Handling Events

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:

Adding event listeners

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.

Preventing default behavior

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 a POST or PUT request to a REST API when the form is submitted. To keep this example as simple as possible, the window.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:

submitForm = (e) => {
  e.preventDefault();
  window.alert('Handling form submission...');
}

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 a SyntheticEvent object type. You’ll learn more about this object type in just a bit.

Using this in event handlers

Earlier, 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:

showAlert = () => {
  console.log(this);
}

Now when the button is clicked, you’ll see the AlertButton component printed to the console:

console alert button
console alert button

To break the this keyword, you can rewrite the showAlert event handler method to be a regular class method:

showAlert() {
  console.log(this);
}

Now when the button is clicked, you’ll see undefined printed to the console:

console undefined
console undefined

Reviewing class methods and the this keyword

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

const displayName = p.displayName.bind(p);
displayName(); // Jane Smith

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;

Understanding the class property + arrow function pattern

To review, the pattern of defining an event handler method using a class property in combination with an arrow function looks like this:

showAlert = () => {
  console.log(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.

Pick an approach and be consistent

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.

The SyntheticEvent object

Earlier, 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.

What you learned

In this article, you learned how to:


React Forms

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:

Creating a simple form

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

Defining the render method

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

Adding state to the component

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:

<input id='name' type='text' onChange={this.nameOnChange} value={name} />

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:

nameOnChange = (e) => {
  // `e` is a `SyntheticEvent` object.
}

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:

nameOnChange = (e) => {
  const name = e.target.value;
}

With the current form field value in hand, call the this.setState method to update the corresponding state value:

nameOnChange = (e) => {
  const name = e.target.value;
  this.setState({ name });
}

With a little refactoring, you can condense the event handler method to a single line of code:

nameOnChange = (e) => {
  this.setState({ name: e.target.value });
}

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

onchange event handler
onchange event handler

Handling form submissions

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:

<form onSubmit={this.onSubmit}>
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.

Controlled components

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:

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.

Handling multiple elements

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:

onChange = (e) => {
  const { name, value } = e.target;
}

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:

onChange = (e) => {
  const { name, value } = e.target;
  this.setState({ [name]: 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!

Adding a text area

In a regular HTML form, the value for a <textarea> element is defined by its inner content:

<textarea>This is the value for the text area element.</textarea>

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;

Adding a select list

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:

ContactUs.defaultProps = {
  phoneTypes: [
    'Home',
    'Work',
    'Mobile',
  ],
};

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;

Implementing validations

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.

Using a validation library

You can also use a validation library like Validator.js to add more sophisticated form validations.

First, install the validator npm package:

npm install validator

Then import the email validator into the ./src/ContactUs.js module:

import isEmail from 'validator/es/lib/isEmail';

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

Client-side vs server-side validation

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.

What you learned

In this article, you learned how to:


Component Lifecycle

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

The lifecycle of a React component

Each class component has several lifecycle methods that you can add to run code at specific points in a component’s lifetime:

Let’s take a closer look the process that occurs when a component is mounting, updating, and unmounting.

Mounting

When a class component is being added to the component tree, the following process occurs:

Updating

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:

When a the setState method is called, the following process occurs:

Unmounting

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.

Avoiding the legacy lifecycle methods

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.

Using the class component lifecycle methods

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!

component lifecycle demo
component lifecycle demo

Fetching data from an API

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:

component lifecycle fetching data

What you learned

In this article, you learned how to:

See also…

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.


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.

Reinforcing your new 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.

Extending your knowledge

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!


Calculator Project

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.

Phase 1: Setup

Begin by using the create-react-app package. You’ll use the command below to create a React application.

npx create-react-app my-app --template @appacademy/simple

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.

Phase 2: Creating the Calculator component

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

State

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:

constructor(props) {
  super(props);
  this.state = {
  // TODO: Set default state
  };
}

Phase 3: Rendering in your component

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:

  render() {
    return (
      <div>
        <h1>Result: {this.state.result}</h1>
      </div>
    );
  }

Phase 4: Creating input fields

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.

handleFirstNum = e => {
  console.log(e);
};

Add an <input> element underneath your rendered result. Assign the onChange prop to a handleFirstNum() callback like so:

<input onChange={this.handleFirstNum} placeholder="First number" />

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.

handleFirstNum = e => {
  // TODO: Parse value
  // TODO: Set 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.

Phase 5: Creating calculator buttons

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.

Clear

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.

Phase 6: Refactoring your project

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!

render(){
  // TODO: destructuring state variables
  return (
    // TODO: refactor variables defined
  );
}

Congratulations! You’ve created your first React application!

What you’ve learned

In this small project, you created your first React class-based component and used it to store state and handle events.


React Widgets Project

Live Demo!

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:

Phase 1: Setup

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:

import React from 'react';

const Root = () => (
  <div>

  </div>
);

export default Root;

Phase 2: Clock Widget

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:

this.state = {
  time: new Date()
};

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:

componentDidMount() {
  this.interval = setInterval(this.tick, 1000);
}

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.

Styling

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.

Phase 3: Folder 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

<Folder folders={folders} />

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.

selectTab = (idx) => {
  console.log(idx)
}

Let’s move forward with rendering the folder titles!

Creating a subcomponent

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:

<Headers
  titles={titles}
  currentTab={this.state.currentTab}
  selectTab={this.selectTab}
/>

Now let’s dive into what your Headers component should render. Begin by returning an unordered list:

const Headers = (props) => {
  return (
    <ul className='tab-header'>

    </ul>
  );
}

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:

const handleClick = (e) => {
  const idx = parseInt(e.target.id, 10);
  selectTab(idx);
}

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.

selectTab = (idx) => {
  this.setState({ currentTab: idx });
}

Styling

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.

Using class names to focus

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:

const headerClass = (idx === currentTab) ? 'active' : '';

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

Phase 4: Weather Widget

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:

this.state = {
  weather: null
};

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:

navigator.geolocation.getCurrentPosition(success[, error[, [options]])

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:

const success = (res) => console.log(res);
navigator.geolocation.getCurrentPosition(success);

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:

navigator.geolocation.getCurrentPosition(success);

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.

Phase 5: Autocomplete Widget

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.

Styling

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.

Bonus Phase: React-Transitions

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?

Bonus Phase: Resolving StrictMode Warnings

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!