DEV Community

SpencerLindemuth
SpencerLindemuth

Posted on

Ref's can change the flow of the game. Don't overReact

What are ref's?

   Traditionally ref's are the the people in black and white striped shirts who blow whistles and get paid a lot of money to not understand what pass interference is. Ref's are also a really cool feature in React to help manage child component updates without using the traditional flow of updating state, passing props and triggering a re-render, but are also incredibly useful in creating concrete references to instances of components and DOM Nodes.

How does it work?

  Finding and using data from DOM nodes is pretty straightforward in JavaScript and React. Use a document.querySelector or document.getElementById whenever you need to reference a node. But what happens when we need to reference a node all across the application? You end up writing a lot of query selectors because the reference usually only survives for the life of the function they are found in. Here is an example of a simple page that focuses the input on button click using query selectors:

import React from 'react';
import './App.css';

export default class App extends React.Component{

  constructor(props){
    super(props)
  }

  focusInput = () => {
    let input = document.getElementById("name-input")
    input.focus()
  }

  render(){
      return (
        <div className="App">
          <label htmlFor="name-input">Please input your name</label>
          <br />
          <input id="name-input"/>
          <br />
          <button onClick={this.focusInput}>Set Input Field Focus</button>
        </div>
      );
    }
}

Seems pretty simple. But what happens when we want to make it a controlled input?
We need to reference the value of the target node's event on change. But let's get add another feature too, and add a button to clear the input field...

import React from 'react';
import './App.css';

export default class App extends React.Component{

  constructor(props){
    super(props)
    this.state = {
      input: ""
    }
  }

  focusInput = () => {
    let input = document.getElementById("name-input")
    input.focus()
  }

  handleChange = (ev) => {
    this.setState({
      input: ev.target.value
    })
  }

  clearInput = () => {
    this.setState({
      input: ""
    })
  }

  render(){
      return (
        <div className="App">
          <label htmlFor="name-input">Please input your name</label>
          <br />
          <input id="name-input" value={this.state.input} onChange={this.handleChange} value={this.state.input}/>
          <br />
          <button onClick={this.focusInput}>Set Input Field Focus</button>
          <button onClick={this.clearInput} >Clear Input</button>
        </div>
      );
    }
}

Ok so we've added some state, some click listeners and we are referencing the input field value with the event target value and we clear the input by clearing the state value the input is tied to. But when we click the clear button, the input field loses focus! This isn't very intuitive. How can we refocus? We would write another query selector and use the .focus() method, but since we are such good programmers, we already have a focus method written for us, so we can just call that after setting state! Now our app is working flawlessly and it only took 42 lines! Nice!

Can we improve on that?

  Ref's provide a global access to the node or instance to be referenced or interacted with anywhere in the class and this node is then able to be passed around to be referenced and interacted upon by other components. Ref's are able to do this by being declared in the constructor with the .createRef() method as seen here:

import React from 'react';
import './App.css';

export default class App extends React.Component{

  constructor(props){
    super(props)
    this.textInput = React.createRef()
    this.state = {
      input: ""
    }
  }

  handleChange = () => {
    this.setState({
      input: this.textInput.current.value
    })
  }

  clearInput = () => {
    this.setState({
      input: ""
    })
    this.textInput.current.focus()
  }

  render(){
      return (
        <div className="App">
          <label htmlFor="name-input">Please input your name</label>
          <br />
          <input ref={this.textInput} value={this.state.input} onChange={this.handleChange}/>
          <br />
          <button onClick={() => this.textInput.current.focus()}>Set Input Field Focus</button>
          <button onClick={this.clearInput}>Clear Input Field</button>
        </div>
      );
    }
}

This code does the same thing as the previous code but using Refs. It's 39 lines, which isn't a huge improvement, but a penny paid is a penny saved in my book. Let's break down what's changed. We create the ref in the constructor, so the input field is referenceable everywhere in out class. For example, in the onClick() method of the focus button, we don't need to write an external function and a query selector. We simply reference the textInput ref and use the .focus() method. Now in this code we only "find" the DOM node once, when it's built, versus 3 times in the previous code. In this code we also drop the ev.target.value we used before and directly reference the current value of the input field.

Other advantages

  Those were obviously crude examples where we didn't gain a large view of the advantage of refs, but it showed syntax and ease of implementation. So what are the larger scale advantages? One of the biggest is stepping away from HTML id's for locating elements, which can change, creating lots of "find and replace"-ing by referencing the instance of the node itself. Another advantage is readability and ease of access. For example, with form validations, if a user presses submit, but forgot to enter their address, it's very easy to highlight the error field, and focus the cursor there to intuitively point the error out.

Things to note

  Ref's don't work with functional components. This is because functional components do not have a this context (aka they don't have an instance). So you cannot reference the functional component like a class component. You can however use ref's inside a functional component by declaring them at the top of the function as a global variable. Another note is that when referring to a node with a ref, the node itself is stored in the .current method as seen above in this.textInput.current.focus().

Conclusion

  Refs are a great tool providing easy access to DOM elements and passing nodes around, but at the end of the day, they are at risk of being overused. They take components and actions out of the traditional dataflow of React, avoiding the almighty rule that state changes should re-render components, which characterizes React as a framework. So be careful with all this newfound knowledge and use them only when necessary! Usually in the context of forms and accessibility.

Top comments (0)