useRef( ) : DOM and beyond...

shubham_sns

Shubham Sananse

Posted on April 28, 2021

useRef( ) : DOM and beyond...

This blog assumes that you know the React fundamentals and useState hook.

Read my article on what is a state in an App? and useState hook (there's a TL;DR section, if you want to have a quick look ๐Ÿ˜‰), you would need some of that state-related concept in this blog.

What is useRef?

Ref is just a { current: initialValue } object. It's nothing special. Both useRef(initivalValue) and createRef() give you that. - Dan Abramov

image.png

function useRef(initialValue) {
  // useRef works more like this
  return React.useState({
    current: initialValue
  })[0]
}
Enter fullscreen mode Exit fullscreen mode

You just created your own useRef. ๐Ÿคฏ๐Ÿคฏ๐Ÿคฏ

Why do we need useRef?

useRef actually serves two purposes,

  • Provides a reference to the DOM elements
  • Returns mutable value which persists across renders

But, what is this mutable and persistent value?

Persistent value is the kind of value that stays persistent between renders, that's what useState returns, a persistent value (state) and updater API (setState) to update that state which causes a re-render for that component. for an application to update its View(UI) you need that setState API.

But what if you want to have a value that stays persistent and does not cause a re-render of a Component.๐Ÿค”

This is such a fundamental need that react provides a built-in API for it, That's what useRef is for.


useRef

// you can set any type of data as initialValue same as useState()
const objectWithCurrentProperty = React.useRef(initialValue)

const refOne = React.useRef() // returns {current : undefined}
const refTwo = React.useRef(1) // returns {current : 1}
const refThree = React.useRef([]) //returns {current : []}
Enter fullscreen mode Exit fullscreen mode

useRef takes the initial value as an argument for the returned value.
These return values would be persisted and you can also mutate them according to your need.

Accessing the DOM with useRef

There are some libraries (e.g. Three.js or Anime.js) that need access to the dom.
when we write jsx it gets converted into React.createElement. a <div>Hello World</div> which we write as jsx gets converted into React.createElement("div", null, "Hello World") so you don't have any direct access to the DOM nodes from your returned jsx.

So to get access to the DOM, you need to ask React to give you access to a particular DOM node when it renders your component. The way this happens is through a special prop called ref.

function UploadButton({ handleUpload }) {
  const inputRef = React.useRef();

  const handleClick = () => inputRef.current.click();

  return (
    <>
      <input type="file" hidden ref={inputRef} onInput={handleUpload} />
      <button onClick={handleClick}>Upload</button>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

In this example, we are passing the ref prop inputRef to the hidden file input, and when we click on a button that uses inputRef.current to get access to that DOM element of that input and on that element we are calling click event.

some other cases would be like getting a value from an input, changing focus, or selecting text.

react-hook-form is a form library that encourages uncontrolled way of handling forms and ref prop to handle forms in react component, give it a try it's awesome.

Mutable Data Storage

Before we try to understand what this is and why do we need this? If you can, I would suggest you create a stopwatch component with stop and resume functionality.

.
.
.

Spoilers ahead....

.
.
.

// Here is a simple StopWatch component which updates time every 1 second
function StopWatch() {
  const [time, setTime] = useState(0);

  useEffect(() => {
      const interval = setInterval(() => {
        setTime((s) => s + 1);
      }, 1000);

      // clearInterval before unmounting component 
      return () => clearInterval(interval);
  }, []);

  return (<div>{time}</div>);
}
Enter fullscreen mode Exit fullscreen mode

But now we need a button which will make the ticking of time stop and resume, for that we would add ticking state and update our useEffect.

function StopWatch() {
  const [time, setTime] = useState(0);
  const [ticking, setTicking] = useState(false);

  useEffect(() => {
    if (ticking) {
      const interval = setInterval(() => {
        setTime((ms) => ms + 1)
      }, 1000);
      return () => clearInterval(interval);
    } else {
      // ๐Ÿค” but we don't have access "interval" here
      clearInterval(interval)
    }
  }, [ticking]);

  return (
    <div>
      <div>{time}</div>
      <button onClick={() => setTicking(c => !c)}>{ticking ? 'Pause' : 'Resume'}</button>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

So where do we put our interval now? if you put this outside useEffect on every render all local variables would reset and it would become undefined again

function StopWatch() {
 ...
// I ๐Ÿ‘‡ would keep becoming undefined on every re-render ๐Ÿ˜ข
let interval;

useEffect ...
}
Enter fullscreen mode Exit fullscreen mode

So now we want something that stays persistent across renders and doesn't cause re-renders, and you guessed it right, we need useRef here.

function StopWatch() {
  const [time, setTime] = useState(0)
  const [ticking, setTicking] = useState(false)
  // mutable and persistant 
  const interval = useRef()

  useEffect(() => {
    if (ticking) {
      // `interval` would not reset when component re-renders
      interval.current = setInterval(() => {
        setTime((ms) => ms + 1)
      }, 1000)
      return () => clearInterval(interval.current)
    } else {
      // and now you have access to the interval
      interval.current && clearInterval(interval.current)
    }
  }, [ticking])

  return (
    <div className="App">
      <h1>{time}</h1>
      <button onClick={() => setTicking(c => !c)}>
        {time === 0 ? 'Start' : ticking ? 'Pause' : 'Resume'}
      </button>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Where to go from here? from now you can go and read more about forwarding refs and useImperativeHandle. Thanks for reading.

๐Ÿ’– ๐Ÿ’ช ๐Ÿ™… ๐Ÿšฉ
shubham_sns
Shubham Sananse

Posted on April 28, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

ยฉ TheLazy.dev

About