useImperativeHandle
is the magic word. More about it later!
In React, the way of communicating between parent and child components is usually done in 2 ways:
props
to the childAn example of using props
:
// Parent component
const MyPage = () => {
return <Modal open={true} title="Submit your score" />;
};
// Child component
const Modal = (props: ModalProps) => {
const { open, title } = props;
if (!open) {
return null;
}
return (
<div className="modal">
<h3>{title}</h3>
...
</div>
);
};
And an example of using a callback function:
// Parent component
const MyPage = () => {
const [isOpen, setIsOpen] = useState<boolean>(false);
return (
<Modal
open={isOpen}
title="Submit your score"
// This is the callback function
toggleOpen={() => setIsOpen(!isOpen)}
/>
);
};
// Child component
const Modal = (props: ModalProps) => {
const { open, title, toggleOpen } = props;
if (!open) {
return null;
}
return (
<div className="modal">
<button onClick={toggleOpen}>Close</button>
<h3>{title}</h3>
...
</div>
);
};
Sometimes you encounter a situation where you have functions in the child component, that need to be called from a parent component. For example I recently encountered this when creating the react-drawio package.
The package I created contained a few functions to interact with the embedded iframe. I wanted to expose those functions to any component that would implement the react-drawio
package.
I was looking for a solution when I stumbled upon the React useImperativeHandle
hook. The React docs describe the hook like this: useImperativeHandle
is a React Hook that lets you customize the handle exposed as a ref.
Let me break that down a bit.
You have probably used useRef
before to get access to the API of a certain DOM element. For example to put .focus()
on an element, or to get a certain attribute from an element by using getAttribute()
.
That's all good, but did you know you can use useRef
for accessing the API of a React component?
// Parent component
const MyPage = () => {
const modalRef = useRef<ModalRef>(null);
useEffect(() => {
if (modalRef.current) {
drawioRef.current.myExtraFunction();
}
}, [modalRef.current]);
return (
<Modal ref={modalRef} />
);
};
// Child component
const Modal = forwardRef<ModalRef, ModalProps>((props, ref) => {
const focus = () => {
// Do something
};
const myExtraFunction = () => {
// Do something
};
useImperativeHandle(ref, () => ({
focus,
myExtraFunction
}), []);
return (
<div className="modal">
...
</div>
);
});
Two things to notice here.
forwardRef
function. React docs describe this function as forwardRef
lets your component receive a ref and forward it to a child component.useRef
in the parent component which can now access both focus
and myExtraFunction
from the child component, because I only exposed those 2 functions.This is not a replacement for props
or callback functions, and you might not even need it that often, but just know that it's there!
Let me know if you found this useful, or if you're already using it for other use cases!
Published Aug 29, 2023