Breaking the Rules with useImperativeHandle Hook in React
Intro:
In React, a parent component passes props down to a child component, and if the parent component needs to call a function defined in the child component, it typically must be defined in the parent component and then passed down. However, there is a way to break this rule using the useImperativeHandle
hook.
The useImperativeHandle:
The useImperativeHandle
hook is used to expose functions from a child component to a parent component. The useImperativeHandle
hook takes a ref as its first argument, some handles as its second argument, and optional dependencies if any. useImperativeHandle
returns undefined. It cannot be used alone and must be used with the useRef
hook and the forwardRef
component.
The useRef
hook is used to create a reference to a DOM node or a value that persists across renders. The forwardRef
component is used to pass the ref from the parent component to the child component.
Let's take a look at the basic syntax to understand all those jargon.
Basic Syntax:
useImperativeHandle(ref, createHandle, dependencies?)
// or
useImperativeHandle(ref, () => {
return {
// ... your methods ...
};
}, []);
Examples:
Let's start better understanding by looking at the following diagram. The useRef hook was defined in the parent component and passed as props to the child component. In the child component, we access the ref and passed it down to useImperativeHandle. useImperativeHandle lets us customize the handle exposed as a ref. The createHandle
is a function that takes no arguments and returns the ref handles we want to expose. That ref handle can have any type. Usually, you will return an object with the methods you want to expose. The forwardRef
is used to expose any DOM node to the parent. To do that, wrap your component definition into forwardRef which will receive a ref as the second argument after props.
Parent Component:
import { useRef } from 'react';
import Modal from './Modal';
function App() {
const modalRef = useRef();
const handleOpenModal = () => {
modalRef.current.openModal();
}
console.log('parent rendered')
return (
<main className="App">
<p>Parent Component</p>
<Modal ref={modalRef} />
<button onClick={handleOpenModal}>Open</button>
</main>
);
}
export default App;
Child Component:
import { forwardRef, useImperativeHandle, useState } from "react";
const Modal = (props, ref) => {
const [modalState, setModalState] = useState(false);
useImperativeHandle(ref, () => ({
openModal: () => setModalState(true)
}));
console.log('child rendered')
if (!modalState) return null;
return (
<div className="modal">
<p>This is my modal!</p>
<button onClick={() => setModalState(false)}>Close</button>
</div>
)
}
export default forwardRef(Modal)
Best Practices:
Limit the number of exposed methods
useImperativeHandle
should only be used when necessary. In most cases, it's better to use props and callbacks to communicate between parent and child components.Avoid circular dependencies:
useImperativeHandle
can create circular dependencies between parent and child components.Consider using TypeScript.
Conclusion:
The useImperativeHandle
hook allows you to call a function defined in the child component from the parent component, breaking the typical rule of defining functions in the parent component and passing them down. This hook, used in conjunction with the useRef
hook and the forwardRef
component gives you greater flexibility and control over your components.
Thank you for taking the time to read this blog. I hope it has been informative and has helped you gain a deeper understanding of the topic. If you have any feedback or suggestions for future content, please let me know!
Subscribe to my newsletter
Read articles from Waqas Khan directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Waqas Khan
Waqas Khan
Hi there! My name is Waqas Khan, and I'm a passionate web developer who is always looking to stay ahead of the curve when it comes to the latest technologies and frameworks. Currently, I'm focused on sharpening my skills with Next Js, TypeScript, and TailwindCSS, which I've found to be my favourite stack so far. As a developer, I'm committed to creating the best next generation websites possible. I believe that the key to success in this field is a combination of technical expertise, attention to detail, and a deep understanding of user needs. That's why I'm constantly striving to learn and grow, both as a developer and as a person. When I'm not coding, you can usually find me exploring the great outdoors, reading up on the latest industry trends, or spending time with my family and friends. I believe that a healthy work-life balance is essential for both personal and professional growth, and I try to maintain that balance in everything I do. Overall, I'm a driven, creative, and dedicated individual who is always looking for new challenges and opportunities. Whether you're looking to build a new website from scratch or just need some help with your existing site, I'm confident that I have the skills and expertise to help you achieve your goals. So why not get in touch today and see how we can work together?