Chapter 2. The Life of a Component

Now that you know how to use the ready-made DOM components, it’s time to learn how to make some of your own.

There are two ways to define a custom component, both accomplishing the same result but using different syntax:

  • Using a function (components created this way are referred to as functional components)

  • Using a class that extends React.Component (commonly referred to as class components)

A Custom Functional Component

Here’s an example of a functional component:

const MyComponent = function() {
  return 'I am so custom';
};

But wait, this is just a function! Yes, this is it, the custom component is just a function that returns the UI that you want. In this case, the UI is only text but often you’ll need a little bit more, most likely a composition of other components. Here’s an example of using a span to wrap the text:

const MyComponent = function() {
  return React.createElement('span', null, 'I am so custom');
};

Using your new shiny component in an application is similar to using the DOM components from Chapter 1, except you call the function that defines the component:

ReactDOM.render(
  MyComponent(),
  document.getElementById('app')
);

The result of rendering your custom component is shown in Figure 2-1.

ruar 0201
Figure 2-1. Your first custom component (02.01.custom-functional.html in the book’s repository)

A JSX Version

The same example using JSX would look a little easier to read. Defining the component looks like this:

const MyComponent = function() {
  return <span>I am so custom</span>;
};

Using the component the JSX way looks like the following, regardless of how the component itself was defined (with JSX or not).

ReactDOM.render(
  <MyComponent />,
  document.getElementById('app')
);
Note

Notice that in the self-closing tag <MyComponent /> the slash is not optional. That applies to HTML elements used in JSX too. <br> and <img> are not going to work, you need to close them like <br/> and <img/>.

A Custom Class Component

The second way to create a component is to define a class that extends React.Component and implements a render() function:

class MyComponent extends React.Component {
  render() {
    return React.createElement('span', null, 'I am so custom');
    // or with JSX:
    // return <span>I am so custom</span>;
  }
}

Rendering the component on the page:

ReactDOM.render(
  React.createElement(MyComponent),
  document.getElementById('app')
);

If you use JSX, you don’t need to know how the component was defined (using a class or a function), in both cases using the component is the same:

ReactDOM.render(
  <MyComponent />,
  document.getElementById('app')
);

Which Syntax to Use?

You may be wondering: with all these options (JSX vs. pure JavaScript, a class component vs. a functional one), which one to use? JSX is the most common. And, unless you dislike the XML syntax in your JavaScript, the path of least resistance and of less typing is to go with JSX. This book uses JSX from now on, unless to illustrate a concept. Why then even talk about a no-JSX way? Well, you should know that there is another way and also that JSX is not some special voodoo but rather a thin syntax layer that transforms XML into plain JavaScript function calls such as React.createElement() before sending the code to the browser.

What about class vs functional components? This is a question of preference. If you’re comfortable with object-oriented programming (OOP) and you like how classes are laid out, then by all means, go for it. Functional components are a little less typing usually, they feel more native to JavaScript (classes in JavaScript were an afterthought and merely syntax sugar) and a little lighter on the computer’s CPU. Historically, functional components were not able to accomplish everything that classes could. Until the invention of hooks, which we’ll get to in due time. This book teaches you both ways and doesn’t decide for you. OK, maybe there’s a slight preference towards functional components.

Properties

Rendering hard-coded UI in your custom components is perfectly fine and has its uses. But the components can also take properties and render or behave differently, depending on the values of the properties. Think about the <a> element in HTML and how it acts differently based on the value of the href attribute. The idea of properties in React is similar (and so is the JSX syntax).

In class components all properties are available via the this.props object. Let’s see an example:

class MyComponent extends React.Component {
  render() {
    return <span>My name is <em>{this.props.name}</em></span>;
  }
}
Note

As demonstrated in this example, you can open curly braces and sprinkle JavaScript values (and expressions too) within your JSX. You’ll learn more about this behavior as you progress with the book.

Passing a value for the name property when rendering the component looks like this:

ReactDOM.render(
  <MyComponent name="Bob" />,
  document.getElementById('app')
);

The result is shown in Figure 2-2.

ruar 0202
Figure 2-2. Using component properties (02.05.this.props.html)

It’s important to remember that this.props is read-only. It’s meant to carry on configuration from parent components to children, it’s not a general-purpose storage of values. If you feel tempted to set a property of this.props, just use additional local variables or properties of your component’s class instead (meaning use this.thing as opposed to this.props.thing).

Properties in Functional Components

In functional components, there’s no this (in JavaScript’s strict mode) or it refers to the global object (in non-strict, dare we say sloppy, mode). So instead of this.props, you get a props object passed to your function as the first argument.

const MyComponent = function(props) {
  return <span>My name is <em>{props.name}</em></span>;
};

A common pattern is to use JavaScript’s destructuring assignment and assign the property values to local variables. In other words the example above becomes:

// 02.07.props.destructuring.html
const MyComponent = function({name}) {
  return <span>My name is <em>{name}</em></span>;
};

You can have as many properties as you want. If, for example, you need two properties name and job you can use them like:

// 02.08.props.destruct.multi.html
const MyComponent = function({name, job}) {
  return <span>My name is <em>{name}</em>, the {job}</span>;
};
ReactDOM.render(
  <MyComponent name="Bob" job="engineer"/>,
  document.getElementById('app')
);

Default Properties

Your component may offer a number of properties, but sometimes a few of the properties may have default values that work well for the most common cases. You can specify default property values using defaultProps property for both functional and class components.

Functional:

const MyComponent = function({name, job}) {
  return <span>My name is <em>{name}</em>, the {job}</span>;
};
MyComponent.defaultProps = {
  job: 'engineer',
};
ReactDOM.render(
  <MyComponent name="Bob" />,
  document.getElementById('app')
);

Class components:

class MyComponent extends React.Component {
  render() {
    return (
      <span>My name is <em>{this.props.name}</em>,
      the {this.props.job}</span>
    );
  }
}
MyComponent.defaultProps = {
  job: 'engineer',
};
ReactDOM.render(
  <MyComponent name="Bob" />,
  document.getElementById('app')
);

In both cases, the result is the output: “My name is Bob, the engineer”

Tip

Notice how the render() method’s return statement wraps the returned value in parentheses. This is just because of JavaScript’s automatic semi-colon insertion (ASI) mechanism. A return statement followed by a new line is the same as return; which is the same as return undefined; which is the definitely not what you want. Wrapping the returned expression in parentheses allows for better code formatting while retaining the correctness.

State

The examples so far were pretty static (or “stateless”). The goal was just to give you an idea of the building blocks of composing your UI. But where React really shines (and where old-school browser DOM manipulation and maintenance gets complicated) is when the data in your application changes. React has the concept of state, which is any data that components want to use to render themselves. When state changes, React rebuilds the UI without you having to do anything. After you build your UI initially in your render() method (or in the rendering function in case of a functional component) all you care about is updating the data. You don’t need to worry about UI changes at all. After all, your render method/function has already provided the blueprint of what the component should look like.

Note

“Stateless” is not a bad word, not at all. Stateless components are much easier to manage and think about. In fact, whenever you can, prefer to go stateless. But applications are complicated and you do need state. So let’s proceed.

Similarly to how you access properties via this.props, you read the state via the object this.state. To update the state, you use this.setState(). When this.setState() is called, React calls the render method of your component (and all of its children) and updates the UI.

The updates to the UI after calling this.setState() are done using a queuing mechanism that efficiently batches changes. Updating this.state directly can have unexpected behavior and you shouldn’t do it. Just like with this.props, consider the this.state object read-only, not only because it’s semantically a bad idea, but because it can act in ways you don’t expect. Similarly, don’t ever call this.render() yourself—instead, leave it to React to batch changes, figure out the least amount of work, and call render() when and if appropriate.

A Textarea Component

Let’s build a new component—a textarea that keeps count of the number of characters typed in (Figure 2-3).

ruar 0203
Figure 2-3. The end result of the custom textarea component

You (as well as other future consumers of this amazingly reusable component) can use the new component like so:

ReactDOM.render(
  <TextAreaCounter text="Bob" />,
  document.getElementById('app')
);

Now, let’s implement the component. Start first by creating a “stateless” version that doesn’t handle updates; this is not too different from all the previous examples:

class TextAreaCounter extends React.Component {
  render() {
    const text = this.props.text;
    return (
      <div>
        <textarea defaultValue={text}/>
        <h3>{text.length}</h3>
      </div>
    );
  }
}
TextAreaCounter.defaultProps = {
  text: 'Count me as I type',
};
Note

You may have noticed that the <textarea> in the preceding snippet takes a defaultValue property, as opposed to a text child node, as you’re accustomed to in regular HTML. This is because there are some slight differences between React and old-school HTML when it comes to form elements. These are discussed further in the book, but rest assured, there are not too many of them. Additionally , you’ll find that these differences make sense and make your life as a developer easier.

As you can see, the TextAreaCounter component takes an optional text string property and renders a textarea with the given value, as well as an <h3> element that displays the string’s length. If the text property is not supplied, the default “Count me as I type” value is used.

Make it Stateful

The next step is to turn this stateless component into a stateful one. In other words, let’s have the component maintain some data (state) and use this data to render itself initially and later on update itself (re-render) when data changes.

First, you need to set the initial state in the class constructor using this.state. Bear in mind that the constructor is the only place where it’s ok to set the state directly without calling this.setState().

Initializing this.state is required, if you don’t do it, consecutive access to this.state in the render() method will fail.

In this case it’s not necessary to initialize this.state.text with a value as you can fallback to the property this.prop.text (try 02.12.this.state.html in the book’s repo):

class TextAreaCounter extends React.Component {
  constructor() {
    super();
    this.state = {};
  }
  render() {
    const text = 'text' in this.state ? this.state.text : this.props.text;
    return (
      <div>
        <textarea defaultValue={text} />
        <h3>{text.length}</h3>
      </div>
    );
  }
}
Note

Calling super() in the constructor is required before you can use this.

The data this component maintains is the contents of the textarea, so the state has only one property called text, which is accessible via this.state.text. Next you need a way to update the state. You can use a helper method for this purpose:

onTextChange(event) {
  this.setState({
    text: event.target.value,
  });
}

You always update the state with this.setState(), which takes an object and merges it with the already existing data in this.state. As you might guess, onTextChange() is an event handler that takes an event object and reaches into it to get the contents of the textarea input.

The last thing left to do is update the render() method to set up the event handler:

render() {
  const text = 'text' in this.state ? this.state.text : this.props.text;
  return (
    <div>
      <textarea
        value={text}
        onChange={event => this.onTextChange(event)}
      />
      <h3>{text.length}</h3>
    </div>
  );
}

Now whenever the user types into the textarea, the value of the counter updates to reflect the contents (Figure 2-4).

ruar 0204
Figure 2-4. Typing in the textarea (02.12.this.state.html)

Note that <teaxarea defaultValue...> in now <textarea value...>. This is because of the way inputs work in HTML where their state is maintained by the browser. But React can do better. In this example implementing onChange means that the textarea is now controlled by React. More on controlled components is coming further in the book.

A Note on DOM Events

To avoid any confusion, a few clarifications are in order regarding the line:

onChange={event => this.onTextChange(event)}

React uses its own synthetic events system for performance, as well as convenience and sanity reasons. To help understand why, you need to consider how things are done in the pure DOM world.

Event Handling in the Olden Days

It’s very convenient to use inline event handlers to do things like this:

<button onclick="doStuff">

While convenient and easy to read (the event listener is right there with the UI code), it’s inefficient to have too many event listeners scattered like this. It’s also hard to have more than one listener on the same button, especially if said button is in somebody else’s “component” or library and you don’t want to go in there and “fix” or fork their code. That’s why in the DOM world it’s common to use element.addEventListener to set up listeners (which now leads to having code in two places or more) and event delegation (to address the performance issues). Event delegation means you listen to events at some parent node, say a <div> that contains many buttons, and you set up one listener for all the buttons, instead of one listener per button. Hence you delegate the event handling to a parent authority.

With event delegation you do something like:

<div id="parent">
  <button id="ok">OK</button>
  <button id="cancel">Cancel</button>
</div>

<script>
document.getElementById('parent').addEventListener('click', function(event) {
  const button = event.target;

  // do different things based on which button was clicked
  switch (button.id) {
    case 'ok':
      console.log('OK!');
      break;
    case 'cancel':
      console.log('Cancel');
      break;
    default:
      new Error('Unexpected button ID');
  };
});
</script>

This works and performs fine, but there are drawbacks:

  • Declaring the listener is further away from the UI component, which makes code harder to find and debug

  • Using delegation and always switch-ing creates unnecessary boilerplate code even before you get to do the actual work (responding to a button click in this case)

  • Browser inconsistencies (omitted here) actually require this code to be longer

Unfortunately, when it comes to taking this code live in front of real users, you need a few more additions if you want to support old browsers:

  • You need attachEvent in addition to addEventListener

  • You need const event = event || window.event; at the top of the listener

  • You need const button = event.target || event.srcElement;

All of these are necessary and annoying enough that you end up using an event library of some sort. But why add another library (and study more APIs) when React comes bundled with a solution to the event handling nightmares?

Event Handling in React

React uses synthetic events to wrap and normalize the browser events, which means no more browser inconsistencies. You can always rely on the fact that event.target is available to you in all browsers. That’s why in the TextAreaCounter snippet you only need event.target.value and it just works. It also means the API to cancel events is the same in all browsers; in other words, event.stopPropagation() and event.preventDefault() work even in old versions of Internet Explorer.

The syntax makes it easy to keep the UI and the event listeners together. It looks like old-school inline event handlers, but behind the scenes it’s not. Actually, React uses event delegation for performance reasons.

React uses camelCase syntax for the event handlers, so you use onClick instead of onclick.

If you need the original browser event for whatever reason, it’s available to you as event.nativeEvent, but it’s unlikely that you’ll ever need to go there.

And one more thing: the onChange event (as used in the textarea example) behaves as you’d expect: it fires when the user types, as opposed to after they’ve finished typing and have navigated away from the field, which is the behavior in plain DOM.

Event-Handling Syntax

The example above used an arrow function to call the helper onTextChange event:

onChange={event => this.onTextChange(event)}

This is because the shorter onChange={this.onTextChange} wouldn’t have worked.

Another option is to bind the method, like so:

onChange={this.onTextChange.bind(this)}

And yet another option, and a common pattern, is to bind all the event handling methods in the constructor:

constructor() {
  super();
  this.state = {};
  this.onTextChange = this.onTextChange.bind(this);
}
// ....
<textarea
  value={text}
  onChange={this.onTextChange}
/>

It’s a bit of necessary boilerplate, but this way the event handler is bound only once, as opposed to every time the render() method is called, which helps reduce the memory footprint of your app.

Props Versus State

Now you know that you have access to this.props and this.state when it comes to displaying your component in your render() method. You may be wondering when you should use versus the other.

Properties are a mechanism for the outside world (users of the component) to configure your component. State is your internal data maintenance. So if you consider an analogy with object-oriented programming, this.props is like a collection of all the arguments passed to a class constructor, while this.state is a bag of your private properties.

In general, prefer to split your application in a way that you have fewer stateful components and more stateless ones.

Props in Initial State: An Anti-Pattern

In the textarea example above it was tempting to use this.props inside of the constructor to set this.state:

this.state = {
  text: this.props.text,
};

This is considered an anti-pattern. Ideally, you use any combination of this.state and this.props as you see fit to build your UI in your render() method. But sometimes you want to take a value passed to your component and use it to construct the initial state. There’s nothing wrong with this, except that the callers of your component may expect the property (text in the preceding example) to always have the latest value and the code above would violate this expectation. To set expectation straight, a simple naming change is sufficient—for example, calling the property something like defaultText or initialValue instead of just text:

Note

Chapter 4 illustrates how React solves this for its implementation of inputs and textareas where people may have expectations coming from their prior HTML knowledge.

Accessing the Component from the Outside

You don’t always have the luxury of starting a brand-new React app from scratch. Sometimes you need to hook into an existing application or a website and migrate to React one piece at a time. Luckily, React was designed to work with any pre-existing codebase you might have. After all, the original creators of React couldn’t stop the world and rewrite an entire huge application (Facebook.com) completely from scratch, especially in the early days when React was young.

One way to have your React app communicate with the outside world is to get a reference to a component you render with ReactDOM.render() and use it from outside of the component:

const myTextAreaCounter = ReactDOM.render(
  <TextAreaCounter text="Bob" />,
  document.getElementById('app')
);

Now you can use myTextAreaCounter to access the same methods and properties you normally access with this when inside the component. You can even play with the component using your JavaScript console (Figure 2-5).

ruar 0205
Figure 2-5. Accessing the rendered component by keeping a reference

In this example, myTextAreaCounter.state checks the current state (empty initially), myTextAreaCounter.props checks the properties and this line sets a new state:

myTextAreaCounter.setState({text: "Hello outside world!"});

This line gets a reference to the main parent DOM node that React created:

const reactAppNode = ReactDOM.findDOMNode(myTextAreaCounter);

This is the first child of the <div id="app">, which is where you told React to do its magic.

Note

You have access to the entire component API from outside of your component. But you should use your new superpowers sparingly, if at all. It may be tempting to fiddle with the state of components you don’t own and “fix” them, but you’d be violating expectations and cause bugs down the road because the component doesn’t anticipate such intrusions.

Lifecycle Methods

React offers several so-called lifecycle methods. You can use the lifecycle methods to listen to changes in your component as far as the DOM manipulation is concerned. The life of a component goes through three steps:

  • Mounting - the component is added to the DOM initially

  • Updating - the component is updated as a result of calling setState()

  • Unmounting - the component is removed from the DOM

React does part of its work before updating the DOM, this is also called rendering phase. Then it updates the DOM and this phase is called a commit phase. With this background let’s consider some lifecycle methods:

  • After the initial mounting and after the commit to the DOM, the method componentDidMount() of your component is called, if it exists. This is the place to do any initialization work that requires the DOM. Any initialization work that does not require the DOM should be in the constructor. And most of your initialization shouldn’t require the DOM. But in this method you can, for example, measure the height of the component that was just rendered, add any event listeners (e.g. addEventListener('resize')), or fetch data from the server.

  • Right before the component is removed from the DOM, the method componentWillUnmount() is called. This is the place to do any cleanup work you may need. Any event handlers, or anything else that may leak memory, should be cleaned up here. After this, the component is gone forever.

  • Before the component is updated, e.g. as a result of setState(), you can use getSnapshotBeforeUpdate(). This method receives the previous properties and state as arguments. And it can return a “snapshot” value, which is any value you want to pass over to the next lifecycle method, which is…

  • componentDidUpdate(previousProps, previousState, snapshot). This is called whenever the component was updated. Since at this point this.props and this.state have updated values, you get a copy of the previous ones. You can use this information to compare the old and the new state and potentially make more network requests if necessary.

  • And then there’s shouldComponentUpdate(newProps, newState) which is an opportunity for an optimization. You’re given the state-to-be which you can compare with the current state and decide not to update the component, so its render() method is not called.

Of these, componentDidMount() and componentDidUpdate() are the most common ones.

Lifecycle Example: Log It All

To better understand the life of a component, let’s add some logging in the TextAreaCounter component. Simply implement all of the lifecycle methods to log to the console when they are invoked, together with any arguments:

componentDidMount() {
  console.log('componentDidMount');
}
componentWillUnmount() {
  console.log('componentWillUnmount');
}
componentDidUpdate(prevProps, prevState, snapshot) {
  console.log('componentDidUpdate     ', prevProps, prevState, snapshot);
}
getSnapshotBeforeUpdate(prevProps, prevState) {
  console.log('getSnapshotBeforeUpdate', prevProps, prevState);
  return 'hello';
}
shouldComponentUpdate(newProps, newState) {
  console.log('shouldComponentUpdate  ', newProps, newState);
  return true;
}

After loading the page, the only message in the console is “componentDidMount”.

Next, what happens when you type “b” to make the text “Bobb”? (See Figure 2-6.) shouldComponentUpdate() is called with the new props (same as the old) and the new state. Since this method returns true, React proceeds with calling getSnapshotBeforeUpdate() passing the old props and state. This is your chance to do something with them and with the old DOM and pass any resulting information as a snapshot to the next method. For example this is an opportunity to do some element measurements or a scroll position and snapshot them to see if they change after the update. Finally, componentDidUpdate() is called with the old info (you have the new one in this.state and this.props) and any snapshot defined by the previous method.

ruar 0206
Figure 2-6. Updating the component

Let’s update the textarea one more time, this time typing “y”. The result is shown on Figure 2-7.

ruar 0207
Figure 2-7. One more update to the component

Finally, to demonstrate componentWillUnmount() in action (using the example 02.14.lifecycle.html from this book’s GitHub repo) you can type in the console:

ReactDOM.render(React.createElement('p', null, 'Enough counting!'), app);

This replaces the whole textarea component with a new <p> component. Then you can see the log message “componentWillUnmount” in the console (Figure 2-8).

ruar 0208
Figure 2-8. Removing the component from the DOM

Paranoid State Protection

Say you want to restrict the number of characters to be typed in the textarea. You should do this in the event handler onTextChange(), which is called as the user types. But what if someone (a younger, more naive you?) calls setState() from the outside of the component? (Which, as mentioned earlier, is a bad idea.) Can you still protect the consistency and well-being of your component? Sure. You can do the validation in componentDidUpdate() and if the number of characters is greater than allowed, revert the state back to what it was. Something like:

componentDidUpdate(prevProps, prevState) {
  if (this.state.text.length > 3) {
    this.setState({
      text: prevState.text || this.props.text,
    });
  }
}

The condition prevState.text || this.props.text is in place for the very first update when there’s no previous state.

This may seem overly paranoid, but it’s still possible to do. Another way to accomplish the same protection is by leveraging shouldComponentUpdate():

shouldComponentUpdate(_, newState) {
  return newState.text.length > 3 ? false : true;
}

See 02.15.paranoid.html in the book’s repo to play with these concepts.

Lifecycle Example: Using a Child Component

You know you can mix and nest React components as you see fit. So far you’ve only seen ReactDOM components (as opposed to custom ones) in the render() methods. Let’s take a look at another simple custom component to be used as a child.

Let’s isolate the counter part into its own component. After all, divide and conquer is what it’s all about!

First, let’s isolate the lifestyle logging into a separate class and have the two components inherit it. Inheritance is almost never warranted when it comes to React because for UI work composition is preferable and for non-UI work a regular JavaScript module would do. But this is just for education and for demonstration that it is possible. And also to avoid copy-pasting the logging methods.

This is the parent:

class LifecycleLoggerComponent extends React.Component {
  static getName() {}
  componentDidMount() {
    console.log(this.constructor.getName() + '::componentDidMount');
  }
  componentWillUnmount() {
    console.log(this.constructor.getName() + '::componentWillUnmount');
  }
  componentDidUpdate(prevProps, prevState, snapshot) {
    console.log(this.constructor.getName() + '::componentDidUpdate');
  }
}

The new Counter component simply shows the count. It doesn’t maintain state, but displays the count property given by the parent.

class Counter extends LifecycleLoggerComponent {
  static getName() {
    return 'Counter';
  }
  render() {
    return <h3>{this.props.count}</h3>;
  }
}
Counter.defaultProps = {
  count: 0,
};

The textarea component sets up a static getName() method:

class TextAreaCounter extends LifecycleLoggerComponent {
  static getName() {
    return 'TextAreaCounter';
  }
  // ....
}

And finally, the textarea’s render() gets to use <Counter/> and use it conditionally; if the count is 0, nothing is displayed.

render() {
  const text = 'text' in this.state ? this.state.text : this.props.text;
  return (
    <div>
      <textarea
        value={text}
        onChange={this.onTextChange}
      />
      {text.length > 0
        ? <Counter count={text.length} />
        : null
      }
    </div>
  );
}
Note

Notice the conditional statement in JSX. You wrap the expression in {} and conditionally render either <Counter/> or nothing (null). And just for demonstration: another option is to move the condition outside the return. Assigning the result of a JSX expression to a variable is perfectly fine.

render() {
  const text = 'text' in this.state
    ? this.state.text
    : this.props.text;
  let counter = null;
  if (text.length > 0) {
    counter = <Counter count={text.length} />;
  }
  return (
    <div>
      <textarea
        value={text}
        onChange={this.onTextChange}
      />
      {counter}
    </div>
  );
}

Now you can observe the lifecycle methods being logged for both components. Open 02.16.child.html from the book’s repo in your browser to see what happens when you load the page and then change the contents of the textarea.

During initial load, the child component is mounted and updated before the parent. You see in the console log:

Counter::componentDidMount
TextAreaCounter::componentDidMount

After deleting two characters you see how the child is updated, then the parent:

Counter::componentDidUpdate
TextAreaCounter::componentDidUpdate
Counter::componentDidUpdate
TextAreaCounter::componentDidUpdate

After deleting the last character, the child component is completely removed from the DOM:

Counter::componentWillUnmount
TextAreaCounter::componentDidUpdate

Finally, typing a character brings back the counter component to the DOM:

Counter::componentDidMount
TextAreaCounter::componentDidUpdate

Performance Win: Prevent Component Updates

You already know about shouldComponentUpdate() and saw it in action. It’s especially important when building performance-critical parts of your app. It’s invoked before componentWillUpdate() and gives you a chance to cancel the update if you decide it’s not necessary.

There is a class of components that only use this.props and this.state in their render() methods and no additional function calls. These components are called “pure” components. They can implement shouldComponentUpdate() and compare the state and the properties before and after an update and if there aren’t any changes, return false and save some processing power. Additionally, there can be pure static components that use neither props nor state. These can straight out return false.

React offers a way to make it easier to use the common (and generic) case of checking all props and state in shouldComponentUpdate(). Instead of repeating this work you can have your components inherit React.PureComponent instead of React.Component. This way you don’t need to implement shouldComponentUpdate(), it’s done for you. Let’s take advantage and tweak the previous example.

Since both components inherit the logger, all you need is:

class LifecycleLoggerComponent extends React.PureComponent {
  // ... no other changes
}

Now both components are pure. Let’s also add a log message in the render() methods:

render() {
  console.log(this.constructor.getName() + '::render');
  // ... no other changes
}

Now loading the page (02.17.pure.html from the repo) prints out:

TextAreaCounter::render
Counter::render
Counter::componentDidMount
TextAreaCounter::componentDidMount

Changing “Bob” to “Bobb” gives us the expected result of rendering and updating.

TextAreaCounter::render
Counter::render
Counter::componentDidUpdate
TextAreaCounter::componentDidUpdate

Now if you paste the string “LOLz” replacing “Bobb” (or any string with 4 characters), you see:

TextAreaCounter::render
TextAreaCounter::componentDidUpdate

As you see there’s no reason to re-render <Counter>, because its props have not changed. The new string has the same number of characters.

Whatever Happened to Functional Components?

You may have noticed that functional components dropped out of this chapter by the time this.state got involved. They come back later in the book, when you’ll also learn the concept of hooks. Since there’s no this in functions, there needs to be another to accomplish the same state management of a component. The good news is that once you understand the concepts of state and props, the functional component differences are just syntax.

As much “fun” as it was to spend all this time on a textarea, let’s move on to something more interesting, before introducing any new concepts. In the next chapter, you’ll see where React’s benefits come into play - namely focusing on your data and have the UI updates take care of themselves.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.82.58.213