React Refs: key to DOM Manipulation Part-2
Naresh
Posted on October 3, 2023
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>
);
};
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>
);
}
);
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} />
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
- ref: the reference we received from a parent
- createHandle: A function that will return the methods or value we want to expose to the parent component.
- 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])
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>
);
});
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
-
forwardRef
allows exposing the child DOM nodes to the parent component. -
useImperativeHandle
helps to provide restricted access to the parent component.
References
- React Docs
Posted on October 3, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.