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 ref
s 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:
<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:
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:
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:
value.errors
object in a lifecycle method, and if one exists, focus the appropriate field.
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:
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:
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.
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!
thank you James. This is a great article. Looking forward to more React tutorials from you.
I agree with Robert, really nice explanation. I learn a lot thx to your posts. I’m waiting for the new posts with excitation! Thx!
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!
Brilliant article, the documentation for React is so awful
isMounted is an Antipattern:
https://facebook.github.io/react/blog/2015/12/16/ismounted-antipattern.html
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?
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.
Aweeeeeeeeeeeesome Article… Thanks for sharing much much better than React Documentation