React.js By Example: Interacting with the DOM

This article is based on the code from part 2 of my Learn Raw React series: Ridiculously Simple Forms.

Sometimes, the user’s next action is so obvious that it feels almost rude not to do it for them.

The textbook example is when a form has failed validation after “submit” was pressed, and the user needs to correct their mistake. What you’ll often find is that despite the obviousness of this, the user has to manually click on that field.

This is stupid. If the user liked clicking that much, they’d be playing Diablo.

If there is only one field with an error, and the user has to fix it, then the field should be focused for them. But how do we automatically focus fields with React?

Well, assuming we’ve got a reference to the <input> element’s DOM node, it is as simple as calling the focus() method. Ta-da! But React uses the Virtual DOM, not the real DOM! Help?

Accessing DOM Nodes

React provides two ways to access DOM nodes. Let’s start with the easiest:

this.refs

Just like its well-known siblings this.props and this.state, this.refs is an object available to each component instance. It’s purpose is to provide access to the DOM nodes associated with rendered elements.

To access an element’s DOM node using this.refs, you’ll first need to let React know that you actually care about that particular node. Do this by passing the element a unique ref:

render: function() {
  return <input placeholder='Email' ref='email' onFocus={this.onEmailFocus} />;
},

Once the element has a ref, you can access its DOM node under this.refs. For example:

onEmailFocus: function() {
  // Automatically defocus the input on focus (muahaha)
  this.refs.email.blur();
},

There is one exception to this behaviour – this.refs only points to a DOM node for refs on HTML elements. When you add a ref to a custom component, it’ll point to the component instance instead. So how do you access the DOM in this case?

ReactDOM.findDOMNode(componentInstance)

Given a component instance, this function will return the DOM node belonging to the outermost HTML element returned by render.

This is a bit of a mouthful, so let’s check your understanding. Given this component:

render: function() {
    return <div><input /></div>;
},

What will the value returned by ReactDOM.findDOMNode(this) be? Touch or hover over this box for an answer:

The returned DOM node will reference the <div> element.

And that’s all there is to accessing DOM nodes! Let’s test your knowledge with a quick quiz.

DOM Access Quiz

Let’s say I’ve just created a new <input> element inside my render function:

render: function() {
  var input = <input ref="name" />;

  // ...

  return input;
},

If I ask you to replace the ... comment with code to focus the new <input>, what should you do? Check your answer by touching or hovering your mouse over this box:

You should tell me I’m crazy.

Before an element is rendered, it won’t actually be in the document yet. Or, in React parlance, it won’t have been mounted.

Until the element is mounted, there is no DOM node to call focus() on!

But if we can’t access DOM nodes for React elements immediately after they’ve been created, how do we know when we can use them?

Lifecycle Methods

It turns out that render isn’t the only method which React understands; you can also give your component class lifecycle methods which React will call at appropriate times. These let you set up the DOM after mounting, clean it up after unmounting, and do a bunch of other things:

componentWillMount: function()
componentDidMount: function()
componentWillReceiveProps: function(nextProps)
componentWillUpdate: function(nextProps, nextState)
componentDidUpdate: function(prevProps, prevState)
componentWillUnmount: function()

Let’s say you wanted to re-implement the HTML5 autoFocus attribute. Can you guess which of the above lifecycle methods you’d use? If you’re unsure, use the React component documentation to find out. Then check your answer by touching or hovering over this box:

componentDidMount: function() {
    this.refs.input.focus();
},

If you don’t like the idea of trying to remember these long and indistinctive method names every time you need to hook into the component lifecycle, print out the PDF cheatsheet which you’ll receive immediately after signing up to my newsletter:

Get the PDF cheatsheet!

But returning to our form validation example, we still don’t have a complete solution. While we have learnt how to be notified when a DOM node becomes available, we don’t actually want to use our ref immediately after mounting. Instead, we want to use it after our props are updated with a new error message.

The problem is that this may happen when the component isn’t mounted; for example, when React is used server-side. How can we ensure that we don’t cause an exception by trying to focus the element before there is a DOM node?

this.isMounted

This is where this.isMounted comes in. This instance variable will always reflect the state of your component: true when your component has a DOM node, and false otherwise. It is generally best practice to check that this.isMounted is true before trying to access the DOM.

So now that you know about accessing the DOM, lifecycle methods, and this.isMounted, let’s get stuck into fixing the original problem!

Exercises

Say we’ve got the following contact form component:

var ContactForm = React.createClass({
  propTypes: {
    value: React.PropTypes.object.isRequired,
    onChange: React.PropTypes.func.isRequired,
    onSubmit: React.PropTypes.func.isRequired,
  },

  onEmailInput: function(e) {
    this.props.onChange(Object.assign({}, this.props.value, {email: e.target.value}));
  },

  onNameInput: function(e) {
    this.props.onChange(Object.assign({}, this.props.value, {name: e.target.value}));
  },

  onSubmit: function(e) {
    e.preventDefault();
    this.props.onSubmit();
  },

  render: function() {
    var errors = this.props.value.errors || {};

    return (
      React.createElement('form', {onSubmit: this.onSubmit, className: 'ContactForm', noValidate: true},
        React.createElement('input', {
          type: 'email',
          className: errors.email && 'ContactForm-error',
          placeholder: 'Email',
          onInput: this.onEmailInput,
          value: this.props.value.email,
        }),
        React.createElement('input', {
          type: 'text',
          className: errors.name && 'ContactForm-error',
          placeholder: 'Name',
          onInput: this.onNameInput,
          value: this.props.value.name,
        }),
        React.createElement('button', {type: 'submit'}, "Add Contact")
      )
    );
  },
});

The app validates that there is input in both the email and name fields. It also validates that the e-mail at least pretends to be a real email. Any error messages are placed in an errors object on this.value – these error objects are used to add an error className for any invalid inputs.

Exercise 1

Currently, the user has to manually click the e-mail address field before they can start typing. Let’s do them a favour and automatically focus the field.

Once you’ve made this work, check your solution against mine:

JS Bin on jsbin.com

Exercise 2

If the user clicks submit but validation fails, we should automatically focus the first invalid field for them. Try implementing this, and then compare your solution with mine. If you need a hint, touch or hover over this box:

Check for a new value.errors object in a lifecycle method, and if one exists, focus the appropriate field.

JS Bin on jsbin.com

Exercise 3

When a contact is successfully added, the submit button will take focus. It would make more sense if focus returned to the e-mail field. Implement this, and then compare your solution with mine:

JS Bin on jsbin.com

And there you have it, you’re now a DOM interaction master!

And as part of that, you’ve now experienced the frustration of looking up lifecycle methods. Seriously, I’ve been using React since ancient times, and I still need my cheatsheet to look them up:

React Cheatsheet

Actually, if you sign up for my newsletter, you’ll get three print-optimised cheatsheats. On top of the React cheatsheet, you’ll also get my ES6 and JavaScript promises cheatsheets. And as a bonus, you’ll also stay in the loop about new articles and re-usable components.

I will send you useful articles, cheatsheets and code.

I won't send you useless inbox filler. No spam, ever.
Unsubscribe at any time.

Thanks for reading! One more thing – I love hearing from readers. If you have something to say, send @james_k_nelson a tweet, or send me an e-mail at james@jamesknelson.com. Happy Reacting!

Read More

8 Comments React.js By Example: Interacting with the DOM

  1. dpg5000

    dude, you are AMAZING. Keep up the great content. I’ve learned more from your posts than a dozen paid-for webcasts and webinars.

    thanks! Seriously, thanks!

    Reply
  2. tomwang

    i think maybe we could remove isMounted from Exercise 2; As i know, componentDidUpdate means the component’s updates are flushed to the DOM, i.e. it is ALREADY mounted. Is it right?

    Reply
  3. Anteka

    Thank so you much for your articles. I couldn’t find anything in the official documentation that explained how refs worked in such a straightforward manner. Everything I saw failed to explain it in simple terms and always presented the concept in tandem with more complex examples. This, along with your tutorial on using React without JSX, have proven invaluable. Thank you again.

    Reply

Leave a Reply

Your email address will not be published.