12

Accessing DOM Elements in React

Sometimes you want to access properties and methods on an HTML element directly. In our React-colored world, where JSX represents everything that is good and pure about markup, why would you ever want to deal directly with the horribleness that is HTML? As you will find out (if you haven’t already), in many cases, dealing with HTML elements through the JavaScript DOM API directly is easier than fiddling with “the React way” of doing things. To highlight one such situation, take a look at the Colorizer example in Figure 12.1.

A figure shows a white square. A text box for entering a color value is present below the square along with a "go" button to its right.

Figure 12.1 Colorizer example.

If you have access to a browser, you can view it live at the following location: https://www.kirupa.com/react/examples/colorizer.htm.

The Colorizer colorizes the (currently) white square with whatever color you provide it. To see it in action, enter a color value inside the text field and click/tap the Go button. (If you don’t have any idea of what color to enter, yellow is a good one.) After you provide a color and submit it, the white square turns whatever color value you provided (see Figure 12.2).

The text "yellow" is entered in the text box and the "go" button is selected. The square is filled with a yellow color.

Figure 12.2 The white square turns yellow.

The fact that the square changes color for any valid color value you submit is pretty awesome, but that isn’t what you should focus on. Instead, pay attention to the text field and the button after you submit a value. Notice that the button gets focus, and the color value you just submitted is still displayed inside the form. If you want to enter another color value, you need to explicitly return focus to the text field and clear out whatever current value is present. Eww! That seems unnecessary, and we can do better than that from a usability point of view.

Wouldn’t it be great if we could both clear the existing color value and return focus to the text field immediately after submitting a color? That would mean that if we submitted a color value of purple, afterward we would see something that looks like Figure 12.3.

The square appears purple, and the text box is empty for the next color value to be entered.

Figure 12.3 We get purple and the text field is ready for the next color.

The entered value of purple is cleared and the focus is returned to the text field. This allows us to enter additional color values and submit them easily without having to keep jumping back and forth between the text field and the button. Isn’t that much nicer?

Getting this behavior right using JSX and traditional React techniques is hard. We aren’t even going to bother explaining how to go about it. On the other hand, getting this behavior right by dealing with the JavaScript DOM API on various HTML elements directly is pretty easy. Guess what we’re going to do? In the following sections, we’re going to use something known as refs, which React provides to help us access the DOM API on HTML elements. We’ll also look at portals, which allow us to render content to any HTML element on the page.

The Colorizer Example

To explain refs and portals, we’ll be modifying the Colorizer example you saw earlier. The code for it looks as follows:

Take a few moments to look through the code and see how it maps to our example. You shouldn’t find anything surprising here. Once you’ve gotten a good understanding of this code, it’s time to learn about refs.

Meet Refs

As you know very well by now, inside our various render methods we’ve been writing HTML-like things known as JSX. Our JSX is simply a description of what the DOM should look like. It doesn’t represent actual HTML, despite looking a whole lot like it. To provide a bridge between JSX and the final HTML elements in the DOM, React provides us with something funnily named as refs (short for references).

The way refs works is a little odd. The easiest way to make sense of it is to just use it. Take a look at just the render method from our Colorizer example:

Inside this render method, we are returning a big chunk of JSX representing (among other things) the input element where we enter our color value. We want to access the input element’s DOM representation so that we can call some APIs on it using JavaScript.

The way we do that using refs is by setting the ref attribute on the element whose HTML we want to reference:

Because we’re interested in the input element, our ref attribute is attached to it. Right now, our ref attribute is empty. What you typically set as the ref attribute’s value is a JavaScript callback function. This function gets called automatically when the component housing this render method gets mounted. If we set our ref attribute’s value to a simple JavaScript function that stores a reference to the referenced DOM element, it would look something like the following highlighted lines:

The end result of this code running once our component mounts is simple: We can access the HTML representing our input element from anywhere inside our component by using self._input. Take a few moments to see how the highlighted lines of code help do that. When you’re done, we’ll walk through this code together.

First, our callback function looks as follows:

This anonymous function gets called when our component mounts, and a reference to the final HTML DOM element is passed in as an argument. We capture this argument using the el identifier, but you can use any name for this argument that you want. The body of this callback function simply sets a custom property called _input to the value of our DOM element. To ensure that we create this property on our component, we use the self variable to create a closure—the this in question refers to our component instead of the callback function itself. Phew!

Let’s focus on what we can do now that we have access to our input element. Our goal is to clear the contents of our input element and give focus to it once the form gets submitted. The code for doing that will live in our setNewColor method, so add the following highlighted lines:

Calling this._input.value = "" clears the color we entered. We set focus back to our input element by calling this._input.focus(). All our ref-related work was to simply enable these two lines; we needed some way to have this._input point to the HTML element representing our input element that we defined in JSX. Then we can just call the value property and focus method that the DOM API exposes on this element.

Simplifying Further with ES6 Arrow Functions

Learning React is hard enough, so I’ve tried to avoid forcing you to use ES6 techniques by default. When it comes to working with the ref attribute, using arrow functions to deal with the callback function simplifies matters a bit. This is one of those cases for which I recommend you use an ES6 technique.

As you saw a few moments ago, to assign a property on our component to the referenced HTML element, we did something like this:

To deal with context shenanigans, we created a self variable initialized to this, to ensure that we created the _input property on our component. That seems unnecessarily messy.

Using arrow functions, we can simplify all of this down to just the following:

The end result is identical to what we spent all this time looking at. Because of how arrow functions deal with context, you can use this inside the function body and reference the component without doing any extra work. No need for an outer self variable equivalent!

Using Portals

You need to be aware of one more DOM-related trick. So far, we’ve been dealing with HTML only in the context of what our JSX generates, either from a single component or combined through many components. This means we’re limited by the DOM hierarchy our parent components impose on us. Having arbitrary access to any DOM element anywhere on the page doesn’t seem possible. Or is it? As it turns out, you can choose to render your JSX to any DOM element anywhere on the page; you aren’t limited to just sending your JSX to a parent component. The magic behind this wizardry is a feature known as portals.

The way we use a portal is very similar to what we do with our ReactDOM.render method. We specify the JSX we want to render, and we specify the DOM element we want to render to. To see all of this in action, go back to our example and add the following h1 element as a sibling just above where we have our container div element defined:

Next, add the following style rule inside the style tag to make our h1 element look nicer:

With this style rule added, let’s first preview our app to make sure that the HTML and CSS we added look as expected (Figure 12.4):

The square box (that is filled with a color), along with the text field and the go button are shown. The text "Colorizer" appears above the square.

Figure 12.4 What our example looks like currently!

Here’s what we want to do. We want to change the value of our h1 element to display the name of the color we are currently previewing. The point to emphasize is that our h1 element is a sibling of the container div element where our app is set to render into.

To accomplish what we’re trying to do, go back to our Colorizer component’s render method and add the following highlighted line to the return statement:

Here we’re instantiating a component called ColorLabel and declaring a prop called color with its value set to our bgColor state property. We haven’t created this component yet, so to fix that, add the following lines just above where we have our ReactDOM.render call:

We are referencing our h1 element with the heading variable. That’s old stuff. For the new stuff, take a look at our ColorLabel component’s render method. More specifically, notice what our return statement looks like. We are returning the result of calling ReactDOM.createPortal():

The ReactDOM.createPortal() method takes two arguments: the JSX to print and the DOM element to print that JSX to. The JSX we are printing is just some formatting characters and the color value we passed in as a prop:

The DOM element we are printing all of this to is our h1 element referenced by the heading variable:

When you preview your app and change the color, notice what happens. The color we specified in our input element shows up in the heading (Figure 12.5):

The square appears yellow, and the text over the square reads "Colorizer: yellow." The text field is empty.

Figure 12.5 Our header now contains our color element.

The important part to re-emphasize is that our h1 element is outside the scope of our main React app, which prints to our container div element. By relying on portals, we have direct access to any element in our page’s DOM and can render content into it, bypassing the traditional parent/child hierarchy we’ve been living under so far.

Conclusion

Most of the time, everything you want to do will be within arm’s reach of the JSX you’re writing. Sometimes, though, you need to break free from the box React puts you in. Even though everything we’re creating is rendering to a HTML document, our React app is like a self-sufficient tropical island within the document; you never quite see the actual HTML that lies just beneath the sands. To help you both see the HTML inside the island and make contact with things that live outside the island, we looked at two features, refs and portals. Refs allow you to cut through and access the underlying HTML element behind the JSX. Portals allow you to render your content to any element in the DOM that you have access to. Between these two solutions, you should be able to easily address any need that you have to deal with regards to the DOM directly.

Note: If you run into any issues, ask!

If you have any questions or your code isn’t running like you expect, don’t hesitate to ask! Post on the forums at https://forum.kirupa.com and get help from some of the friendliest and most knowledgeable people the Internet has ever brought together!

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

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