- Published on
React Hooks: useRef & forwardRef
- Authors
- Name
- Curtis Warcup
 
 
Beyond the Basic React Hooks
I think it's safe to say that react developers have used the basic hooks like useState, useEffect, and useRef. But as you start to use react more and more, you will find that there are many more hooks. Here are some of the more advanced hooks that I have found useful.
useRef
Use the useRef hook when you want to "remember" a value, but don't want that value to trigger a re-render. This is useful when you want to store a value that you don't want to change, but you don't want to store it in state.
Examples include:
- A reference to a DOM node- i.e. const inputRef = useRef()and theninputRef.current.focus()
 
- i.e. 
- A reference to a component instance- i.e. const componentRef = useRef()and thencomponentRef.current.someMethod()
 
- i.e. 
Using useRef to store a value
- import - useReffrom react- t 
- inside your component, call - useRefand pass in the initial value
import { useRef } from 'react'
export default function App() {
  const ref = useRef(0)
  console.log(ref.current) // returns 0
  return <div className="App">{ref.current}</div>
}
- You can access the current value of the ref by calling ref.current
- This current value is intentionally mutable. You can change it by assigning a new value to it.- You can read and write to it, but it will not trigger a re-render.
 
import { useRef } from 'react'
export default function App() {
  const ref = useRef(0)
  console.log(ref.current) // returns 0
  ref.current = 1
  console.log(ref.current) // returns 1
  ref.current++ // returns 2
  return <div className="App">{ref.current}</div>
}
Unlike state, ref is a plain JavaScript object contianing the current property. You can add any properties you want to it.
import { useRef } from 'react'
export default function App() {
  const ref = useRef(0)
  ref.current = 1
  ref.current++ // returns 2
  ref.current = 'hello' // returns 'hello'
  ref.current = { name: 'John' }
  return <div className="App">{ref.current.name}</div>
}
When a piece of information is used for rendering, keep it in state. When a piece of information is only needed by event handlers and changing it doesn’t require a re-render, using a ref may be more efficient.
Ref vs State
| ref | state | 
|---|---|
| useRef(initialVal)returns{current: initialVal} | useState(initialVal)returns[currentVal, setVal] | 
| Does not trigger a re-render | Triggers a re-render when you change it | 
| Mutable, meaning you can modify and update currentvalue outside of the rendering process | Immutable, meaning you can only update it with setVal. When this occurs, a re-render will occur. | 
| Should not be used to read or write the currentvalue during rendering | You can read state at any time. However, each render has its own snapshot of state which does not change. | 
useRef is could be though of like a specific flavor of useState. Take the following example:
// Inside of React
function useRef(initialValue) {
  const [ref, unused] = useState({ current: initialValue })
  return ref
}
During the first render, useRef return {current: initialValue}. This is the same as useState({current: initialValue}). However, useState will trigger a re-render when you change the value of current. useRef will not trigger a re-render.
Information obtained from beta.reactjs.
Examples of useRef
import { useState, useRef } from 'react'
export default function App() {
  const [text, setText] = useState('')
  const [isSending, setIsSending] = useState(false)
  const timeoutRef = useRef(null)
  function handleSend() {
    setIsSending(true)
    timeoutRef.current = setTimeout(() => {
      alert('Sent!')
      setIsSending(false)
    }, 3000)
  }
  function handleUndo() {
    setIsSending(false)
    clearTimeout(timeoutRef.current)
  }
  return (
    <>
      <input disabled={isSending} value={text} onChange={(e) => setText(e.target.value)} />
      <button disabled={isSending} onClick={handleSend}>
        {isSending ? 'Sending...' : 'Send'}
      </button>
      {isSending && <button onClick={handleUndo}>Undo</button>}
    </>
  )
}
import { useState, useRef } from 'react'
function DebouncedButton({ onClick, children }) {
  const timeoutRef = useRef(null)
  return (
    <button
      onClick={() => {
        clearTimeout(timeoutRef.current)
        timeoutRef.current = setTimeout(() => {
          onClick()
        }, 1000)
      }}
    >
      {children}
    </button>
  )
}
useRef to store a DOM node
Sometime you may need to access a DOM node directly. For example, you may want to focus on an input element when the page loads, or you may want to measure the size of an element. You can use the useRef hook to store a reference to a DOM node.
Getting a reference to a DOM node
import { useRef } from 'react'
export default function Form() {
  // Create a ref
  const inputRef = useRef(null)
  // Focus on the input element when the page loads
  function handleClick() {
    inputRef.current.focus()
  }
  return (
    <>
      {/* Get a reference to the input element */}
      <input ref={inputRef} />
      {/* When the button is clicked, focus on the input element */}
      <button onClick={handleClick}>Focus the input</button>
    </>
  )
}
Managing a list of refs using a ref callback
Sometimes you might need a ref to each item in the list, and you don’t know how many you will have. Something like this wouldn’t work:
<ul>
  {items.map((item) => {
    // Doesn't work!
    const ref = useRef(null)
    return <li ref={ref} />
  })}
</ul>
You cannot call
useRefin a loop condition, or inside amapcall.
The best solution to this is to pass a function to the ref attribute. This is known as a ref callback. The function will be called with the DOM node as an argument. You can store the node in a ref object.
This allows you to maintain your own array of refs, and you can access them later. You can also create a new Map object to store the refs.
import { useRef } from 'react'
export default function CatFriends() {
  // create a ref object
  const itemsRef = useRef(null)
  function scrollToId(itemId) {
    const map = getMap()
    const node = map.get(itemId)
    node.scrollIntoView({
      behavior: 'smooth',
      block: 'nearest',
      inline: 'center',
    })
  }
  function getMap() {
    if (!itemsRef.current) {
      // Initialize the Map on first usage.
      itemsRef.current = new Map()
    }
    return itemsRef.current
  }
  return (
    <>
      <nav>
        <button onClick={() => scrollToId(0)}>Tom</button>
        <button onClick={() => scrollToId(5)}>Maru</button>
        <button onClick={() => scrollToId(9)}>Jellylorum</button>
      </nav>
      <div>
        <ul>
          {catList.map((cat) => (
            <li
              key={cat.id}
              ref={(node) => {
                const map = getMap()
                if (node) {
                  map.set(cat.id, node)
                } else {
                  map.delete(cat.id)
                }
              }}
            >
              <img src={cat.imageUrl} alt={'Cat #' + cat.id} />
            </li>
          ))}
        </ul>
      </div>
    </>
  )
}
const catList = []
for (let i = 0; i < 10; i++) {
  catList.push({
    id: i,
    imageUrl: 'https://placekitten.com/250/200?image=' + i,
  })
}
forwardRef
If you try to put a ref on your own component, you will get undefined as the value of the ref. This is because the ref is passed to the component as a prop, and the component doesn’t forward it to the DOM node.
For example, this code will not work:
import { useRef } from 'react'
// your own component
function MyInput(props) {
  return <input {...props} />
}
export default function MyForm() {
  // create a ref object in another component
  const inputRef = useRef(null)
  function handleClick() {
    inputRef.current.focus()
  }
  return (
    <>
      {/* Pass the ref to your own component */}
      <input ref={inputRef} />
      <button onClick={handleClick}>Focus the input</button>
    </>
  )
}
The
refprop is not forwarded to the DOM node. When you click the button, the input will not be focused.
This happens because by default React does not let a component access the DOM nodes of other components. Not even for its own children!
Solution: use forwardRef to pass the ref to the DOM node.
Instead, components that want to expose their DOM nodes have to opt in to that behavior. A component can specify that it “forwards” its ref to one of its children. Here’s how MyInput can use the forwardRef API:
import { forwardRef } from 'react'
const MyInput = forwardRef((props, ref) => {
  return <input {...props} ref={ref} />
})
Now, when you pass a ref to <MyInput>, the ref will be forwarded to the <input> element inside it.
import { useRef, forwardRef } from 'react'
// your own component
const MyInput = forwardRef((props, ref) => {
  return <input {...props} ref={ref} />
})
export default function MyForm() {
  // create a ref object in another component
  const inputRef = useRef(null)
  function handleClick() {
    inputRef.current.focus()
  }
  return (
    <>
      {/* Pass the ref to your own component */}
      <MyInput ref={inputRef} />
      <button onClick={handleClick}>Focus the input</button>
    </>
  )
}
It is common to use forwardRef in components like buttons, inputs, and other components that need to be accessed by the parent component.
Best Practices for Refs
- Only use ref's when you need to access the DOM node directly. Don't use it to store data.- examples of when you need to access the DOM node directly:- focus on an input element
- measure the size of an element
- add event listeners to an element
- scroll position
- calling browser APIs that React doesn't support
 
 
- examples of when you need to access the DOM node directly:
- Don't use ref's to store data. UseuseStateinstead.
- Avoid changing DOM nodes managed by React.- Example: if you use ref.current.remove()to remove a DOM node, React will not be able to update it. If you calledsetStateafter that, React will not be able to update the DOM node.
 
- Example: if you use