CHAPTER 2

image

Inside the DOM Abstraction

In the previous chapter, you saw that React abstracts away the DOM, providing a simpler programming model, better performance, and the possibility to render components on the server and even power native mobile apps.

This chapter will cover JSX, the JavaScript language extension used to describe the UI.

Events in React

React implements a synthetic event system that brings consistency and high performance to React applications and interfaces.

It achieves consistency by normalizing events so that they have the same properties across different browsers and platforms.

It achieves high performance by automatically using event delegation. React doesn’t actually attach event handlers to the nodes themselves. Instead, a single event listener is attached to the root of the document; when an event is fired, React maps it to the appropriate component element. React also automatically removes the event listeners when a component unmounts.

DOM Event Listeners

HTML has always provided a beautiful and easy-to-understand event handling API for tag attributes: onclick, onfocus, etc. The problem with this API (and the reason why it is not used in professional projects) is that it’s full of undesirable side effects: it pollutes the global scope, it’s hard to track in the context of a big HTML file, it can be slow, and it can lead to memory leaks, just to name a few issues.

JSX makes use of a similarly easy-to-use and understand API but removes the undesired side effects from the HTML counterpart. Callback functions are scoped to the component (which, as you’ve seen, is responsible for just one part of the UI and tends to contain small markup), and it’s smart enough to use event delegation and auto manage unmounting. Notice, however, that there are some minor differences in contrast with the original HTML implementation. In React, the properties are camel cased (“onClick” instead of “onclick”). Built to be consistent across browsers and devices, it implements a subset of all the variations found in different versions of different browsers. Tables 2-1 through 2-4 show the available events.

Table 2-1. Touch and Mouse Events

Tab1

Table 2-2. Keyboard Events

onKeyDown

onKeyUp

onKeyPress

Table 2-3. Focus and Form Events

onFocus

onBlur

 

onChange

onInput

onSubmit

Table 2-4. Other Events

Tab4

Kanban App: Managing the DOM Event

In the last iteration of the Kanban app, you added the following inline function (using the fat arrow =>) inside the onClick event handler:

<div className="card__title" onClick={
  ()=>this.setState({showDetails: !this.state.showDetails})
}>

This is practical but not very flexible. Let’s change this implementation to use a new method called toggleDetails inside the class to handle the event:

class Card extends Component {
  constructor() {
    super(...arguments);
    this.state = {
      showDetails: false
    };
  }

  toggleDetails() {
    this.setState({showDetails: !this.state.showDetails});
  }

  render() {
    let cardDetails;
      if (this.state.showDetails) {
        cardDetails = (
          <div className="card__details">
            {this.props.description}
            <CheckList cardId={this.props.id} tasks={this.props.tasks} />
          </div>
        );
      }

    return (
      <div className="card">
        <div className="card__title" onClick={this.toggleDetails.bind(this)}>
          {this.props.title}
        </div>
        {cardDetails}
      </div>
    )
  }
}

Image Note  Earlier React versions (specifically prior to the use of ES6 classes) had a built-in “magic” feature that bound all methods to this automatically. Since this could be confusing for JavaScript developers that are not used to this feature in other classes, it was removed. In the current versions, the developer has to explicitly bind the function to context. This can be done in different ways, the simplest one being to simply use .bind(this) to generate a bound function. Bind and other functional JavaScript methods are discussed in Appendix B.

Digging Deeper in JSX

JSX is React’s optional extension to the JavaScript syntax used for writing declarative XML-style syntax inside JavaScript code.

For web projects, React’s JSX provides a set of XML tags that are similar to HTML, but there are other use cases in which another set of XML tags are used to describe the user interface (such as React with SVG, React Canvas, and React Native).

When transpiled (converted to plain JavaScript, so the browser or server can interpret the code), the XML is transformed into a function call to the React Library.

This

<h1>Hello World</h1>

becomes

React.createElement("h1", null, "Hello World");

The use of JSX is optional. However, embracing it has the following benefits:

  • XML is great for representing UIs in element trees with attributes.
  • It’s more concise and easier to visualize the structure of your application.
  • It’s plain JavaScript. It doesn’t alter the language semantics.

JSX vs. HTML

For web usage, JSX looks like HTML, but it’s not an exact implementation of the HTML specification. React’s creators went so far to make JSX similar enough to HTML so it could be used to describe web interfaces properly, but without losing sight of the fact that it should also conform to JavaScript style and syntax.

Differences Between JSX and HTML

There are three important aspects you should be aware of when writing HTML syntax with JSX:

  1. Tag attributes are camel cased.
  2. All elements must be balanced.
  3. The attribute names are based on the DOM API, not on the HTML language specs.

Let’s review them now.

Tag Attributes Are Camel Cased

For example, in HTML, the input tag can have an optional maxlength attribute:

<input type="text" maxlength="30" />

In JSX, the attribute is written as maxLength (note the uppercase “L”):

return <input type="text" maxLength="30" />

All Elements Must be Balanced

Since JSX is XML, all elements must be balanced. Tags such as <br> and <img>, which don’t have ending tags, need to be self-closed. So, instead of <br>, use <br/> and instead of <img src="...">, use <img src="..." />.

Attribute Names are Based on the DOM API

This can be confusing, but it is actually very easy. When interacting with the DOM API, tag attributes may have different names than those you use in HTML. One of such example is class and className.

For example, given this regular HTML

<div id="box" class="some-class"></div>

if you want to manipulate the DOM and change its class name using plain JavaScript, you would do something like

document.getElementById("box").className="some-other-class"

As far as JavaScript is concerned, that attribute is called className, not class. Since JSX is just a syntax extension to JavaScript, it conforms to the attribute names as defined in the DOM. That same div should be expressed in JSX as

return <div id="box" className="some-class"></div>

JSX Quirks

JSX can be tricky sometimes. This section groups small techniques, tips, and strategies to deal with common problems you may face when building components with JSX.

Single Root Node

React components can only render a single root node. To understand the reasons for this limitation, let’s look at this sample return from a render function:

return(
  <h1>Hello World</h1>
)

It is transformed into a single statement:

return React.createElement("h1", null, "Hello World");

On the other hand, the following code isn’t valid:

return (
   <h1>Hello World</h1>
    <h2>Have a nice day</h2>
 )

To be clear, this is not a JSX limitation, but rather a JavaScript characteristic: a return statement can only return a single value, and in the previous code we were trying to return two statements (two calls to React.createElement). The alternative is very simple: as you would do in plain JavaScript, wrap all return values in a root object.

return (
   <div>
     <h1>Hello World</h1>
     <h2>Have a nice day</h2>
   </div>
)

This works perfectly because it would be transformed into

return React.createElement("div", null,
    React.createElement("h1", null, "Hello World"),
    React.createElement("h2", null, " Have a nice day"),
 )

thus returning a single value, and done via valid JavaScript.

Conditional Clauses

If statements doesn’t fit well in JSX, but what may be seen as a JSX limitation is actually a consequence of the fact that JSX is just plain JavaScript. To better explain, let’s start by reviewing how JSX gets transformed into plain JavaScript.

JSX like

return (
    <div className="salutation">Hello JSX</div>
)

gets transformed into JavaScript like

return (
    React.createElement("div", {className: "salutation"}, "Hello JSX");
)

However, if you try to write an if clause in the middle of the JSX, like

<div className={if (condition) { "salutation" }}>Hello JSX</div>

it would be transformed into an invalid JavaScript expression, as shown here and in Figure 2-1:

React.createElement("div", {className: if (condition) { "salutation"}}, "Hello JSX");

9781484212615_Fig02-01.jpg

Figure 2-1. Syntax error when trying to use an if expression inside JSX

What Are the Alternatives?

Although not being possible to use an “if” statement inside JSX, there are alternatives to render content conditionally, including using ternary expressions and assigning conditionally to a variable (null and undefined values are treated by React and outputs nothing when escaped in JSX).

Use Ternary Expressions

If you have a very simple expression, you can use the ternary form:

render() {
  return (
    <div className={condition ? "salutation" : “”}>
      Hello JSX
    </div>
  )
}

This will be transformed into a valid JS:

React.createElement("div", {className: condition ? "salutation" : “”}, "Hello JSX");

The ternary form also works for conditionally rendering entire nodes:

<div>
  {condition ?
    <span>Hello JSX</span>
  : null}
</div>

Move the Condition Out

If a ternary expression isn’t robust enough for your case, the alternative is to not use conditionals in the middle of JSX. Simply move the conditional’s clauses outside (as you did in Chapter 2 for hiding or showing the ContactItem details).

Instead of

render() {
  return (
    <div className={if (condition) { "salutation" }}>
      Hello JSX
    </div>
  )
}

move the conditional outside of JSX, like

render() {
  let className;
  if(condition){
    className = "salutation";
  }
  return (
    <div className={className}>Hello JSX</div>
  )
}

React knows how to handle undefined values and won’t even create a class attribute in the div tag if the condition is false.

Kanban App: Indicating Whether a Card Is Open or Closed

In the first chapter, you used this technique of moving the condition out for toggling the Card details. Let’s also use the ternary form to add a className conditionally to the Card Title (some of the original code is omitted for brevity). The results are shown in Figure 2-2.

class Card extends Component {
  constructor() { ... }
  toggleDetails() { ... }

  render() {
    let cardDetails;
      if (this.state.showDetails) {
        cardDetails = (
          <div className="card__details">
            {this.props.description}
            <CheckList cardId={this.props.id} tasks={this.props.tasks} />
          </div>
        );
      }

    return (
      <div className="card">
        <div className={
                this.state.showDetails? "card__title card__title--is-open" : "card__title"
           } onClick={this.toggleDetails.bind(this)}>
            {this.props.title}
        </div>
        {cardDetails}
      </div>
    )
  }
}

9781484212615_Fig02-02.jpg

Figure 2-2. Conditional class

Blank Space

This is a very small and quick tip: in HTML, browsers usually render a space between elements in multiple lines. React’s JSX will only render a space if you specifically tell it to do so. For example, the following JSX will render as shown in Figure 2-3.

return (
  <a href="http://google.com">Google</a >
  <a href=“http://facebook.com">Facebook</a>
)

9781484212615_Fig02-03.jpg

Figure 2-3. JSX doesn’t produce a space between lines

To explicitly insert a space, you can use an expression with an empty string {" "}:

return(
  <a href="http://google.com">Google</a>{" "}
  <a href="http://facebook.com">Facebook</a>
)

This renders the desired output, as shown in Figure 2-4.

9781484212615_Fig02-04.jpg

Figure 2-4. Using an expression to render a space

Comments in JSX

Another quirk derived from the fact that JSX isn’t HTML is the lack of support for HTML comments (e.g. <!--  comment -->). Although traditional HTML tag comments are not supported, since JSX is made of JavaScript expressions, it’s possible to use regular JS comments. You just need to be careful to put {} around the comments when you are within the child section of a tag.

let content = (
  <Nav>
    {/* child comment, put {} around */}
    <Person
      /* multi
         line
         comment */
      name={window.isLoggedIn ? window.name : ’’} // end of line comment
    />
  </Nav>
);

Rendering Dynamic HTML

React has built-in XSS attack protection, which means that by default it won’t allow HTML tags to be generated dynamically and attached to JSX. This is generally good, but in some specific cases you might want to generate HTML on the fly. One example would be rendering data in markdown format to the interface.

Image Note  Markdown is a format that allows you to write using an easy-to-read, easy-to-write plain text format. For example, surrounding text with double asterisks will make it strong (bold).

React provides the dangerouslySetInnerHTML property to skip XSS protection and render anything directly.

Kanban App: Rendering Markdown

Let’s see this in action by enabling markdown on the Kanban app Card’s description. You will start by changing the card descriptions on your data model to include some markdown formatting.

let cardsList = [
 {
  id:1,
  title: "Read the Book",
  description: "I should read the **whole** book",
  status: "in-progress",
  tasks: []
 },
 {
  id:2,
  title: "Write some code",
  description: "Code along with the samples in the book. The complete source can be found image
                at [github](https://github.com/pro-react)",
  status: "todo",
  tasks: [
    {id: 1, name:"ContactList Example", done:true},
    {id: 2, name:"Kanban Example", done:false},
    {id: 3, name:"My own experiments", done:false}
  ]
 },
];

You will need a JavaScript library to convert the markdown used in the card descriptions to HTML. There are many open source libraries available. In this example, you’re going to use one called marked (https://github.com/chjj/marked).

If you’re following along with this book’s examples and using a module system, import the library on your package.json and install it (both can be done with the single command npm install --save marked). Don’t forget to import the marked module on the beginning of your file.

Using the marked module, your code will look like this:

import React, { Component } from ’react’;
import CheckList from ’./CheckList’;
import marked from ’marked’;

Then, you’re going to use the function marked() provided by the library to convert the markdown to HTML (I have omitted some code not pertinent to this example for brevity):

class Card extends Component {
  constructor() {...}
  toggleDetails() {...}

  render() {
    let cardDetails;
      if (this.state.showDetails) {
        cardDetails = (
          <div className="card__details">
            {marked(this.props.description)}
            <CheckList cardId={this.props.id} tasks={this.props.tasks} />
          </div>
        );
      }

    return (
      <div className="card">
        <div className={
               this.state.showDetails? "card__title card__title--is-open" : "card__title"
           } onClick={this.toggleDetails.bind(this)>
            {this.props.title}
        </div>
        {cardDetails}
      </div>
    )
  }
}

But as expected, React by default won’t allow any HTML tags to be rendered inside your JSX so the output will look like Figure 2-5.

9781484212615_Fig02-05.jpg

Figure 2-5. React escaping HTML by default

Using dangerouslySetInnerHTML, you can achieve the desired final result, as shown here and in Figure 2-6:

cardDetails = (
  <div className="card__details">
    <span dangerouslySetInnerHTML={{__html:marked(this.props.description)}} />
    <CheckList cardId={this.props.id} tasks={this.props.tasks} />
  </div>
);

9781484212615_Fig02-06.jpg

Figure 2-6. React rendering dynamically generated HTML with dangerouslySetInnerHTML

React Without JSX

JSX brings a concise and familiar syntax for describing UIs as tree structures. It does so by enabling the use of XML inside JavaScript code without altering the semantics of JavaScript. React was designed with JSX in mind; however, it’s absolutely possible to use React without JSX. Although you’ll continue using JSX for all the examples in this book, this section will briefly explore how to work with React without JSX.

React Elements in Plain JavaScript

You can create React elements in plain JavaScript using React.createElement, which takes a tag name or component, a properties object, and variable number of optional child arguments.

let child1 = React.createElement(’li’, null, ’First Text Content’);
let child2 = React.createElement(’li’, null, ’Second Text Content’);
let root = React.createElement(’ul’, { className: ’my-list’ }, child1, child2);
React.render(root, document.getElementById(’example’));

Element Factories

For convenience, React provides short-hand factory functions under React.DOM for common HTML tags. Let’s put this together with a more advanced example:

React.DOM.form({className:"commentForm"},
  React.DOM.input({type:"text", placeholder:"Name"}),
  React.DOM.input({type:"text", placeholder:"Comment"}),
  React.DOM.input({type:"submit", value:"Post"})
)

The above is equivalent to the following JSX:

<form className="commentForm">
  <input type="text" placeholder="Name" />
  <input type="text" placeholder="Comment" />
  <input type="submit" value="Post" />
</form>

Using destructuring assignment, it’s possible to tidy things up for a more concise syntax:

import React, { Component } from ’react’;
import {render} from ’react-dom’;

let {
  form,
  input
} = React.DOM;

class CommentForm extends Component {
  render(){
    return form({className:"commentForm"},
      input({type:"text", placeholder:"Name"}),
      input({type:"text", placeholder:"Comment"}),
      input({type:"submit", value:"Post"})
    )
  }
}

Custom Factories

It’s also possible to create factories for custom components, like so:

let Factory = React.createFactory(ComponentClass);
...
let root = Factory({ custom: ’prop’ });
render(root, document.getElementById(’example’));

Inline Styling

By authoring React components using JSX, you’re combining UI definition (content markup) and interaction (JavaScript) in the same file. As discussed, the separation of concerns in this scenario comes from discrete, well-encapsulated, and reusable components for each concern. But there’s another important factor to user interfaces besides content and interaction: styling.

React provides the capacity to write inline styles using JavaScript. At first, the idea to write styles in JavaScript can seem a little strange, but it can provide some benefits over traditional CSS:

  • Scoped styles without selectors
  • Avoids specificity conflicts
  • Source order independence

Note that JavaScript is highly expressive and so by using it you automatically gain variables, functions, and full range of control flow constructs.

Defining Inline Styles

In React’s components, inline styles are specified as a JavaScript object. Style names are camel cased in order to be consistent with DOM properties (e.g. node.style.backgroundImage). Additionally, it’s not necessary to specify pixel units - React automatically appends the correct unit behind the scenes. The following example shows an example of inline styling in React:

import React, { Component } from ’react’;
import {render} from ’react-dom’;

class Hello extends Component {
  render() {
    let divStyle = {
      width: 100,
      height: 30,
      padding: 5,
      backgroundColor: ’#ee9900’

    };
    return <div style={divStyle}>Hello World</div>
  }
}

Kanban App: Card Color via Inline Styling

While it’s possible to completely ditch CSS in favor of inline styling using JavaScript, generally an hybrid approach is more appropriate, where CSS (or CSS preprocessors such as Sass or Less) is used for major style definitions and inline styling inside React components is used for dynamic, state-based appearance.

In the next steps, you’re going to add custom color to mark a card.

  1. Add color to your data model. First, let’s change the CardsList array to insert the colors:
    let cardsList = [
      {
        id:1,
        title: "Read the Book",
        description: "I should read the book",
        color: ’#BD8D31’,
        status: "in-progress",
        tasks: []
      },
      {
        id:2,
        title: "Write some code",
        description: "Code along with the samples ... at [github](https://github.com/pro-react)",
        color: ’#3A7E28’,
        status: "todo",
        tasks: [
          {id: 1, name:"ContactList Example", done:true},
          {id: 2, name:"Kanban Example", done:false},
          {id: 3, name:"My own experiments", done:false}
        ]
      },
    ];
  2. Pass the color as props to the Card component. The Card’s parent component is the List component. Currently, the List component passes three attributes as props to the Card component: title, description, and tasks. You need to add color as another attribute:
    class List extends Component {
      render() {
        let cards = this.props.cards.map((card) => {
          return <Card id={card.id}
                       title={card.title}
                       description={card.description}
                       color={card.color}
                       tasks={card.tasks} />
        });

        return (
          ...
        )
      }
    }
  3. Create a div with inline style in the Card component. Finally, you need to create an object containing all the style rules and the div that will use the style object inline:
    class Card extends Component {
      constructor() {...}
      toggleDetails() {...}

      render() {
        let cardDetails;
        if (this.state.showDetails) {...}

        let sideColor = {
          position: ’absolute’,
          zIndex: -1,
          top: 0,
          bottom: 0,
          left: 0,
          width: 7,
          backgroundColor: this.props.color
        };

        return (
          <div className="card">
            <div style={sideColor}/>
            <div className={
                    this.state.showDetails? "card__title card__title--is-open" : "card__title"
               } onClick={this.toggleDetails.bind(this)}>
                {this.props.title}
            </div>
            {cardDetails}
          </div>
        )
      }
    }

Figure 2-7 shows the rendered result.

9781484212615_Fig02-07.jpg

Figure 2-7. Inline styles for dynamic card colors

Working With Forms

In React, a component’s internal state is kept to minimum because every time the state changes, the component is rendered again. The purpose of this is to have an accurate representation of the component state in your JavaScript code and let React keep the interface in sync.

For this reason, form components such as <input>, <textarea>, and <option> differ from their HTML counterparts because they can be mutated via user interactions.

React provides two ways of handling forms as components and lets you choose based on your app characteristics or personal preference. The two available ways to handle a form field are as a controlled component or an uncontrolled component.

Controlled Components

A form component with a value or checked prop is called a controlled component. In a controlled component, the value rendered inside the element will always reflect the value of the prop. By default the user won’t be able to change it.

That’s the case for your Kanban cards checklist. If you try clicking one of the task’s checkboxes, it won’t change. They are reflecting the value hardcoded in your cardsList array and will only change if you change the array itself.

Before heading back to the Kanban project, though, let’s see another example. Start with a Search component that contains an input field:

import React, { Component } from ’react’;
import {render} from ’react-dom’;

class Search extends Component {
  render() {
    return (
      <div>
        Search Term: <input type="search" value="React" />
      </div>
    )
  }
}

render(<Search />, document.body);

This will render a form field displaying an immutable value of “React.” Any user input will have no effect on the rendered element because React has declared the value to be ”React,” as shown in Figure 2-8.

9781484212615_Fig02-08.jpg

Figure 2-8. The form element

To be able to make this value change, you need to handle it as a component state. This way, any changes to the state value will be reflected in the interface.

class Search extends Component {
  constructor() {
    super();
    this.state = {
      searchTerm: "React"
    };
  }

  render() {
    return (
      <div>
        Search Term:
        <input type="search" value={this.state.searchTerm} />
      </div>
    )
  }
}

You could even give the end user the ability to update the state value using the onChange event.

class Search extends Component {
  constructor() {
    super();
    this.state = {
      searchTerm: "React"
    };
  }

  handleChange(event) {
    this.setState({searchTerm: event.target.value});
  }

  render() {
    return (
      <div>
        Search Term:
        <input type="search" value={this.state.searchTerm}
                onChange={this.handleChange.bind(this)} />
      </div>
    )
  }
}

This may look like a convoluted way to deal with forms, but it has the following advantages:

  • It stays true to the React way of handling components. The state is kept out of the interface, and is entirely managed in your JavaScript code.
  • This pattern makes it easy to implement interfaces that respond to or validate user interactions. For example, you could very easily limit the user input to 50 characters:
    this.setState({searchTerm: event.target.value.substr(0, 50)});

Special Cases

There are a few special cases to remember when creating controlled form components: TextArea and Select.

TextArea

In HTML, the value of <textarea> is usually set using its children:

<textarea>This is the description.</textarea>

For HTML, this easily allows developers to supply multiline values. However, since React is JavaScript, you do not have string limitations (you use if you want newlines, for example). To keep consistent across other form elements, React uses the value prop to set <textarea> values:

<textarea value="This is a description." />

Select

In HTML, you set the selected option using the “selected” attribute on the option tag. In React, in order to make components easier to manipulate, the following format is adopted instead:

<select value="B">
 <option value="A">Mobile</option>
 <option value="B">Work</option>
 <option value="C">Home</option>
</select>

Uncontrolled Components

Controlled components adhere to React’s principles and have their advantages. While uncontrolled components are an anti-pattern for how most other components are constructed in React, sometimes you don’t need to oversee the user input field by field.

This is especially true in longer forms, where you want to let the user fill in the fields and then process everything when the user is done.

Any input that does not supply a value is an uncontrolled component, and the value of the rendered element will reflect the user’s input. For example,

return (
  <form>
    <div className="formGroup">
      Name: <input name="name" type="text" />
    </div>
    <div className="formGroup">
      E-mail: <input name="email" type="mail" />
    </div>
    <button type="submit">Submit</button>
  </form>
)

will render two input fields that start off with an empty value. Any user input will be immediately reflected by the rendered elements.

Image Tip  If you want to set up an initial value for an uncontrolled form component, use the defaultValue prop instead of value.

It’s still possible to handle uncontrolled component forms using onSubmit, like so:

handleSubmit(event) {
  console.log("Submitted values are: ",
              event.target.name.value,
              event.target.email.value);
  event.preventDefault();
}

render() {
  return (
    <form onSubmit={this.handleSubmit}>
      <div className="formGroup">
        Name: <input name="name" type="text" />
      </div>
      <div className="formGroup">
        E-mail: <input name="email" type="mail" />
      </div>
      <button type="submit">Submit</button>
    </form>
  )
}

Kanban App: Creating a Task Form

Regarding your Kanban app, you already have controlled components: the tasks checkboxes. Let’s add an uncontrolled component this time: a text field to include new tasks.

class CheckList extends Component {
  render(){
    let tasks = this.props.tasks.map((task) => (...);
    return (
      <div className="checklist">
        <ul>{tasks}</ul>
        <input type="text"
               className="checklist--add-task"
               placeholder="Type then hit Enter to add a task" />
      </div>
    )
  }
}

Since you didn’t specify a value prop, the user can freely write inside the text field. In the next chapter, you will wire the form fields in the checklist to add and mark tasks as done.

To finish, let’s add some CSS to style the form element:

.checklist--add-task {
  border: 1px dashed #bbb;
  width: 100%;
  padding: 10px;
  margin-top: 5px;
  border-radius: 3px;
}

Virtual DOM Under the Hood

As you’ve seen so far, one of React’s key design decisions is to make the API seem like it re-renders the whole app on every update. DOM manipulation is a slow task for a variety of reasons, so in order to make this possible in a performant way, React implements a virtual DOM. Instead of updating the actual DOM every time the application state changes, React simply creates virtual tree that looks like the DOM state that you want. Then it figures out how to make the DOM look like this efficiently without recreating all of the DOM nodes.

This process of finding the minimum number of changes that must be made in order to make the virtual DOM tree and the actual DOM tree identical is called reconciliation, and in general it is a very complex and extremely expensive operation. Even after many iterations and optimizations, this remains a very difficult and time-consuming problem. To make this tractable, React makes a few assumptions about how typical applications work that allow for a much faster algorithm in practical use cases. Some assumptions include:

  • When comparing nodes in the DOM tree, if the nodes are of different types (say, changing a div to a span), React is going to treat them as two different sub-trees, throw away the first one, and build/insert the second one.

    The same logic is used for custom components. If they are not of the same type, React is not going to even try to match what they render. It is just going to remove the first one from the DOM and insert the second one.

  • If the nodes are of the same type, there are two possible ways React will handle this:
    • If it’s a DOM element (such as changing <div id="before" /> to <div id="after" />), React will only change attributes and styles (without replacing the element tree).
    • If it’s a custom component (such as changing <Contact details={false} /> to <Contact details={true} />), React will not replace the component. Instead, it will send the new properties to the current mounted component. This will end up triggering a new render() on the component, and the process will reinitiate with the new result.

Keys

Although React’s Virtual DOM and differing algorithms are very smart, in order to be fast, React makes some assumptions and takes a naive approach in some cases. Lists of repeating items are especially tricky to handle. To understand why, let’s start with an example. Listings 2-1 and 2-2 represent a previous and current render.

The difference between the two lists seems pretty obvious, but which is the best approach to transform one list into the other? Adding a new item (Apple) to the beginning of the list and deleting the last one (Banana) is a possible operation, but changing the last item’s name and position is also a possibility. In bigger lists, different possibilities arise and each of them can possibly cause side effects. Considering that nodes can be inserted, deleted, substituted, and moved, it is pretty hard to determine best approaches for all possible cases with an algorithm.

For this reason, React introduced the key attribute. Keys are unique identifiers that allow for fast lookups between trees for finding insertions, deletions, substitutions, and moves. Every time you create components in a loop, it’s a good idea to provide a key for each child in order to help the React Library match and avoid performance bottlenecks.

Kanban App: Keys

Your previous Kanban app example is already warning about child elements without keys in the browser console (see Figure 2-9).

9781484212615_Fig02-09.jpg

Figure 2-9. A React warning about missing key props on the List and Checklist components

The key prop can contain any value that is unique and constant. Your card’s data contains an ID for each card. Let’s use it as the key prop in the List component:

class List extends Component {
  render() {
    let cards = this.props.cards.map((card) => {
      return <Card key={card.id}
                   id={card.id}
                   title={card.title}
                   description={card.description}
                   color={card.color}
                   tasks={card.tasks} />
    });

    return (
      <div className="list">
        <h1>{this.props.title}</h1>
        {cards}
      </div>
    )
  }
}

You also have an array in Checklist. Let’s add a key there, too:

class CheckList extends Component {
  render(){
    let tasks = this.props.tasks.map((task) => (
      <li key={task.id} className="checklist__task">
        <input type="checkbox" defaultChecked={task.done} />
        {task.name}{’ ’}
        <a href="#" className="checklist__task--remove" />
      </li>
    ));
    return (...);
  }
}

Refs

In the React way of working, when rendering a component, you’re always dealing with the virtual DOM. If you change a component’s state or send new props to a child, for example, they are reactively rendered to the virtual DOM. React will then update the actual DOM after the reconciliation phase.

This means that as developers you’re never touching the real DOM. In some cases, though, you may find yourself wanting to "reach out" for the actual DOM markup rendered by a component. Think twice before manipulating the actual DOM because in almost every case there’s a clearer way to structure your code within the React model. However, for the few cases where it still might be necessary or beneficial, React provides an escape hatch known as refs.

Refs can be used as a string prop on any component, like so:

<input ref="myInput" />

The referenced DOM markup can then be accessed via this.refs, like so:

let input = this.refs.myInput;
let inputValue = input.value;
let inputRect = input.getBoundingClientRect();

In this book, we will use refs very sparingly because there are few circumstances where they are really necessary. As an example, let’s create a component consisting of only a text input and a button that, when clicked, focuses the text input:

class FocusText extends Component {
  handleClick() {
    // Explicitly focus the text input using the raw DOM API.
    this.refs.myTextInput.focus();
  }
  render() {
    // The ref attribute adds a reference to the component to
    // this.refs when the component is mounted.
    return (
      <div>
        <input type="text" ref="myTextInput" />
        <input
          type="button"
          value="Focus the text input"
          onClick={this.handleClick.bind(this)}
        />
      </div>
    );
  }
}

Summary

In this chapter, you examined the details about React’s DOM abstraction and the techniques the library uses to achieve fast performance, like event delegation and its diff and reconciliation characteristics (including the need for key props). You also learned about JSX in depth (and how React can be used without JSX, if desired), inline styles, and forms.

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

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