The power of React refs

The power of React refs

In the official React definition “Refs provide a way to access DOM nodes or React elements created in the render method”.

In this article I will show you how Refs can be used for other purposes.

I will not dwell on the explanation of the concept or the various methods of creating refs. You can find all this on the following link: https://reactjs.org/docs/refs-and-the-dom.html

Standard use case

In a common scenario we can use refs like this:

function NormalUsage() {
    const inputRef = React.useRef();

    const handleSubmit = (e) => {
        e.preventDefault();
        console.log(inputRef.current.value);
    };

    return (
        <form onSubmit={handleSubmit}>
            <input type="text" name="input" ref={inputRef} />
            <button> Submit form </button>
        </form>
    );
}

In this case, ref is used as a reference to a DOM Input. inputRef allow us access to input properties like value.

Advanced use — Timeout

The next example is a simple notice component. The notice can be closed by clicking a button, otherwise it will automatically close after a time limit.

const TIMEOUT_LIMIT = 5000

function Notice() {
  const [isVisible, setIsVisible] = React.useState(true)
  const timeoutRef = React.useRef(null)

  const hideNotice = () => {
    setIsVisible(false)
  }

  const forceHideNotice = () => {
    clearTimeout(timeoutRef.current)
    hideNotice()
  }

  React.useEffect(() => {
    timeoutRef.current = setTimeout(hideNotice, TIMEOUT_LIMIT)

    return () => {
      clearTimeout(timeoutRef.current)
    }
  }, [])

  return isVisible ? (
    <div className="Notice">
      <div>Notice content</div>
      <button onClick={forceHideNotice}>Close</button>
    </div>
  ) : null
}

In this case the ref is used to store a mutable data: the timeout ID

The steps are:

  1. Create the ref (line 6)

  2. Assign the timeout value to ref (line 18)

  3. Clear timeout:

    • If notice is closed by the user (line 13)
    • When component is unmounted (line 21)

Advanced use #2— RequestAnimationFrame

The next example is a simple component that renders a div with an animation on the height.

const HEIGHT_LIMIT = 100

function Animated() {
  const divRef = React.useRef()
  const requestRef = React.useRef()
  const heightRef = React.useRef(0)

  const animate = () => {
    heightRef.current += 1
    divRef.current.style.height = `${heightRef.current}px`
    if (heightRef.current < HEIGHT_LIMIT) {
      requestRef.current = requestAnimationFrame(animate)
    }
  }

  React.useEffect(() => {
    requestRef.current = requestAnimationFrame(animate)
    return () => cancelAnimationFrame(requestRef.current)
  }, [])

  return <div ref={divRef} style={{ backgroundColor: "#000" }} />
}

In this example 3 refs was created:

divRef: A classical use of refs. I’ts a reference to the Div element below.

requestRef: It contains the requestAnimationFrame id

heightRef: It contains the updated height value

The animate function is called until heightRef.current reaches the value of HEIGHT_LIMIT. heightRef.current is incremented by 1 each time the function is called.

If the component will be unmounted before heightRef.current reaches the value of HEIGHT_LIMIT, cancelAnimationFrame function will be executed

Advanced use #3 — Custom Hook

The next example creates a hook that returns a ref containing the x, y position of the mouse. With the help of the requestAnimationFrame a rounded div is animated to follow the mouse

//Hook
function useMousePosition() {
  const mousePosition = React.useRef({ x: 0, y: 0 })

   React.useEffect(() => {     
    const updateMousePosition = (ev) => {
      mousePosition.current = { x: ev.clientX, y: ev.clientY }
    }

    window.addEventListener("mousemove", updateMousePosition)
    return () => window.removeEventListener("mousemove", updateMousePosition)     
  }, [])

  return mousePosition
}

//Component
function Point() {
  const mousePosition = useMousePosition()
  const requestRef = React.useRef()
  const pointRef = React.useRef()

  const animate = () => {
    pointRef.current.style.left = `${mousePosition.current.x}px`
    pointRef.current.style.top = `${mousePosition.current.y}px`
    requestRef.current = requestAnimationFrame(animate)
  }
  React.useEffect(() => {
    requestRef.current = requestAnimationFrame(animate)
    return () => cancelAnimationFrame(requestRef.current)
  }, [])

  return (
    <div
      ref={pointRef}
      style={{
        position: "fixed",
        width: "20px",
        height: "20px",
        borderRadius: "50%",
        backgroundColor: "#000",
        transform: "translate(-50%, -50%)"
      }}
    />
  )
}

The usePosition hook adds a mouseMove event to the window and stores the clientX and clientY values. In the animate function of the Point component the values x and y are used to animate the top and left properties of the div referenced by pointRef

Conclusion

React refs are extremely useful for saving mutable data without triggering a re-render of the component. They are powerful even when used in custom hooks and guarantee high performance.