React Refs: key to DOM Manipulation Part-2
Tags: react, webdev, javascript, typescript
Published: 2023-10-03 07:22:42

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.

```tsx
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>
  );
};

```

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.

```tsx
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>
    );
  }
);
```

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.

```tsx
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} />
```
**CodeSandbox**
{% codesandbox vibrant-rhodes-fmly27 %}

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
```tsx
 useImperativeHandle(ref, ()=>{
  return{focus = myInpRef.focus()};
},[myInpRef])
```

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`.

```tsx
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>
  );
});
```

{% codesandbox proud-rain-jtcsp8 %}

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

- React Docs 
  - [forwardRef](https://react.dev/reference/react/forwardRef)
  - [useImperativeHandle](https://react.dev/reference/react/useImperativeHandle)