Harness the Power of useImperativeHandle in React
Introductionlink
React is a vast library that provides various features for building user interfaces. React embraces a modular approach and offers life cycles, hooks, libraries, and other tools to help developers create reusable, modular and testable components.
In this article, we'll introduce one of React's built-in hooks: useImperativeHandle
. We will explore its properties, how to create custom hooks with it, and provide real-world examples to demonstrate its use cases.
What is useImperativeHandle in React?link
The useImperativeHandle
hook in React enables the exposure of a child component's functions to its parent. Suppose you have a child component that possesses a custom feature - and you want to utilize it in the parent component, useImperativeHandle
makes it possible.
This hook only works with components created by React.forwardRef
, where the child component exposes its ref attribute to useImperativeHandle
and allows it to navigate through the DOM to reach the child components' functions and properties.
Here is an example of using useImperativeHandle
with forwardRef
:
import React, { useRef, useImperativeHandle, forwardRef } from 'react';
const ChildComponent = forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
customFunction: () => {
console.log('Do something');
},
}));
return <div>I am a child component</div>;
});
Here, useImperativeHandle
allows the child component, ChildComponent
, to expose its customFunction
to the parent component.
Creating Custom Hooks with useImperativeHandlelink
A notable feature of useImperativeHandle
is its ability to build powerful and reusable custom hooks. When you create a custom hook using useImperativeHandle
, it becomes easy to distribute and reuse relevant parts of your code.
Here is an example of creating a custom hook using useImperativeHandle
:
import { useRef, useImperativeHandle, forwardRef } from 'react';
const useCustomHook = (initialValue) => {
const ref = useRef();
useImperativeHandle(ref, () => ({
customFunction: () => {
console.log('Do something');
},
}));
return [initialValue, ref];
};
const ParentComponent = () => {
const childRef = useRef();
const [value, setValue] = useCustomHook('initial value');
const handleClick = () => {
childRef.current.customFunction();
};
return (
<>
<button onClick={handleClick}>Call Child Function</button>
<ChildComponent ref={childRef} />
</>
);
};
In this example, we defined a custom hook named useCustomHook
. It exposes value
and ref
to the parent component.
Real-World Exampleslink
Let's go through real-world scenarios in which we could use useImperativeHandle
.
Custom Validation Hooks
We can create custom validation hooks that include a ref to the child components, allowing for the call of a child component's validate() function from the parent component.
import { useRef, useImperativeHandle, forwardRef } from 'react';
export const useInputValidation = () => {
const ref = useRef(null);
useImperativeHandle(ref, () => ({
validate: () => {
const val = ref.current.value;
const regex = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i;
const isValid = regex.test(val);
return isValid ? null : 'Invalid Email Address';
},
}));
return ref;
};
const ChildComponent = forwardRef((props, ref) => {
const inputRef = useRef(null);
const handleChange = (e) => {
e.preventDefault();
props.onChange(e.target.value);
});
return (
<<form>
<input type="text" ref={inputRef} />
</form>
);
};
const ParentComponent = () => {
const childRef = useRef(null);
const inputRef = useInputValidation();
const handleSubmit = () => {
const validationMessage = childRef.current.validate();
if (validationMessage) {
alert(validationMessage);
} else {
alert('Submit form!');
}
};
return (
<>
<ChildComponent ref={childRef} />
<button onClick={handleSubmit}>Submit</button>
</>
);
};
In this example, we have created a custom validation hook useInputValidation
. We then have a ChildComponent
add a ref to its input field, which the parent then uses to check the input value's validity.
Interactive Child Components
You can create interactive child components using useImperativeHandle
. For instance, let's consider a Video
component that has a play function, which starts the video at its last playback position. The Video
component can then expose the play function to a parent component, where the playAgain
function can call the child component's play function when a Replay
button is clicked.
import React, { useRef, useImperativeHandle, forwardRef } from 'react';
const Video = forwardRef((props, ref) => {
const videoRef = useRef(null);
const lastPosition = useRef(0);
useImperativeHandle(ref, () => ({
play: () => {
videoRef.current.currentTime = lastPosition.current;
videoRef.current.play();
},
}));
const handleEnded = () => {
lastPosition.current = 0;
};
const handlePause = () => {
lastPosition.current = videoRef.current.currentTime;
};
return (
<video
ref={videoRef}
src={props.src}
onEnded={handleEnded}
onPause={handlePause}
/>
);
});
const ParentComponent = () => {
const videoRef = useRef(null);
const handleReplay = () => {
videoRef.current.play();
};
return (
<>
<Video src="/src/video.mp4" ref={videoRef} />
<button onClick={handleReplay}>Replay</button>
</>
);
};
Here, we have a Video
component that saves the video's last position when it pauses or ends. We can then expose the play
function to the parent component using useImperativeHandle
.
In conclusion, useImperativeHandle
is a powerful hook in React that enables the creation of clean, reusable, and modular code. We previously defined the paradigm of custom hooks and provided examples of two real-world scenarios in which this hook's usage becomes useful. As developers continue to hone their craft at React, they should consider leveraging useImperativeHandle
to gain the best possible results from their code.