DEV Community

Cover image for React Refs: key to DOM Manipulation Part-2
Naresh
Naresh

Posted on

React Refs: key to DOM Manipulation Part-2

In Part-1 of the React Refs, we have seen how to get access to the own component DOM elements. Now we will see how to access other component DOM elements using refs.

React provides a function forwardRef, to expose the DOM element to other components or Parent components.

forwardRef As the name suggests, it forwards the ref to child nodes. This is very useful when building re-usable component libraries.

Usage and Syntax:

Let's say we have a component MyInput that renders an input field. This component will take three props label, textVal and onTextChange.

import { ChangeEvent, FC } from "react";

type MyInputProps = {
  label: string;
  textVal: string;
  onTextChange(text: string): void;
};

export const MyInput: FC<MyInputProps> = (props) => {
  const handleTextChange = (event: ChangeEvent<HTMLInputElement>) => {
    const value = event.target.value;
    props.onTextChange(value);
  };

  return (
    <div>
      <label htmlFor="username">{props.label}</label>
      <input
        name="username"
        value={props.textVal}
        onChange={handleTextChange}
      />
    </div>
  );
};

Enter fullscreen mode Exit fullscreen mode

Now we want to expose the input element to its parent. We do this by wrapping our Component in the forwardRef function. This function forwards the parent component ref to its child DOM Nodes.

import { ChangeEvent, forwardRef } from "react";

type MyInputProps = {
  label: string;
  textVal: string;
  onTextChange(text: string): void;
};

export const MyInput = forwardRef<HTMLInputElement, MyInputProps>(
  function MyInput(props, ref) {
    const handleTextChange = (event: ChangeEvent<HTMLInputElement>) => {
      const value = event.target.value;
      props.onTextChange(value);
    };
    return (
      <div>
        <label htmlFor="username">{props.label}</label>
        <input
          ref={ref}
          name="username"
          value={props.textVal}
          onChange={handleTextChange}
        />
      </div>
    );
  }
);
Enter fullscreen mode Exit fullscreen mode

Now, from the parent component we can modify the input DOM node directly. Let's add a button that will focus our input element on click. To do this we need to create a ref in our parent and pass it to the MyInput component. Then we can call focus() method on the ref current element to focus the input.

const myInputRef = createRef<HTMLInputElement>(null);

// calling focus of the input DOM element when the user clicks on focus button
const handleFocusClick = ()={
   if(myInputRef.current){
     myInputRef.current.focus();
   }
}

// passing ref to MyInput component
<MyInput {...otherProps} ref={myInputRef} />
Enter fullscreen mode Exit fullscreen mode

CodeSandbox

We know how to expose a child DOM node to the Parent component. But what if we want to expose only certain functionalities of the DOM node? Yes, we can do this using imperative handling of refs.

Exposing Limited Access to Parent Components

When we set ref to the child DOM node, we are giving access to the entire DOM node. To restrict access we can use the useImperativeHandle hook. This hook takes three parameters

  1. ref: the reference we received from a parent
  2. createHandle: A function that will return the methods or value we want to expose to the parent component.
  3. dependencies: an optional array to specify the values that we used inside the created handle function. Whenever a value changes in this list react will re-run the createHandle function.

Syntax

 useImperativeHandle(ref, ()=>{
  return{focus = myInpRef.focus()};
},[myInpRef])
Enter fullscreen mode Exit fullscreen mode

Let's build a VideoPlayer Component that will expose play and pause functionality to parent component using forwardRef and useImperativeHandle.

Steps

  • Create a VideoPlayer component with an internal ref variable to the video element.
  • Use the useImperativeHandle to expose the play, pause functions to the parent component ref.
  • In the parent Component, invoke the play and pause methods, when the user clicks.

Why do we need an internal ref element in the Video player? We are already getting the ref from the forwardRef?

We need it because, if we assign the ref to the DOM element react will give the entire DOM node access. To restrict that we create a local ref in our component and then assign the local ref functionality to the parent ref using useImperativeHandle.

import { forwardRef, useImperativeHandle, useRef } from "react";

export type VideoPlayerRef = Partial<Pick<HTMLVideoElement, "play" | "pause">>;

export const VideoPlayer = forwardRef<VideoPlayerRef, {}>(function VideoPlayer(
  _props,
  ref
) {
  const videoRef = useRef<HTMLVideoElement>(null);

  useImperativeHandle(
    ref,
    () => {
      // binding videoRef.current as play and pause uses this
      return {
        play: videoRef.current?.play.bind(videoRef.current),
        pause: videoRef.current?.pause.bind(videoRef.current)
      };
    },
    []
  );

  return (
    <video width="400" ref={videoRef}>
      <source
        src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
        type="video/mp4"
      />
    </video>
  );
});
Enter fullscreen mode Exit fullscreen mode

In the above example, we exposed only the play and pause functionality of the video element to the Parent. With this we can restrict access to DOM elements.

Summary

  1. forwardRef allows exposing the child DOM nodes to the parent component.
  2. useImperativeHandle helps to provide restricted access to the parent component.

References

Top comments (0)