How to Export Instance Methods from React Function Components
“You can have a little imperative React, as a treat.”
When building modern applications with React, Function Components and Hooks are the de facto way to do it. Hooks make your code more declarative and easier to reason about (usually).
However, with all great hammers, you run the risk of everything looking like a nail . Occasionally, some more object-oriented solutions might be the right tool for the job. For example, what if you had a child component that contained a function that needed to be called from the parent? You might want an instance method, but we don’t use Classes anymore!
Fear not – you don’t have to abandon Hooks to be able to call methods on child components. In this post, I’ll show you how – using forwardRef
and useImperativeHandle
.
NOTE: This pattern will probably be of most use to library authors – especially for hiding implementation details and avoiding boilerplate for your users. But, used sparingly, it can be quite helpful for your own projects as well.
First we use forwardRef
useRef
is (probably) most commonly used to get a reference to a DOM element (basically React’s document.querySelector
). Because of this, it’s easy to forget that a ref
can hold anything. In our case, we’re going to store an object which contains our instance method(s). But first, we have to be able to set a ref
on our component, which is where forwardRef
comes in:
// ChildComponent.jsx
import { forwardRef } from "react";
export const ChildComponent = forwardRef((props, ref) => {
// stuff goes here
});
forwardRef
receives one argument: the Function Component you wish to forward a ref
to. However, you may notice that the component itself has two arguments – in addition to props
, it also receives ref
. This is what enables us to create a ref
in a parent component and pass it to the child (this will be relevant later).
Defining our method object
The next thing we do is create an object to hold our method and find a way to tell React to make it our component’s ref
. We do this by using useImperativeHandle
:
// ChildComponent.jsx
import { forwardRef, useImperativeHandle } from 'react';
export const ChildComponent = forwardRef((props, ref) => {
// create our ref object
const publicRef = {
// add any methods or properties here
instanceMethod: () => {
// do cool stuff
},
};
// pass the ref and a function that returns our object
useImperativeHandle(ref, () => publicRef);
return (...)
});
useImperativeHandle
takes two arguments – the ref
passed from the parent and a function which returns whatever you want stored in that ref
.
Accessing the ref
in the Parent component
With the child all set up, we can now use it in other components:
// ParentComponent.jsx
import { useRef } from "react";
import { ChildComponent } from "./ChildComponent";
const ParentComponent = (props) => {
// create a ref to pass
const childRef = useRef();
const wrapperFunction = () => {
// check that the ref exists to avoid errors
if (!childRef.current) return;
childRef.current.instanceMethod();
};
// pass childRef to ChildComponent (thanks to forwardRef)
return <ChildComponent ref={childRef} />;
};
Here, we just create a ref
with useRef
and pass it down to ChildComponent
– and that’s it! Your instance method is now available to use inside childRef.current
. (In the above example, I put the instance method inside wrapperFunction
because checking that ref.current
exists and then typing out ref.current.method()
can get repetitive, but this isn’t necessary.)
It should also be noted at this point that you aren’t limited to exporting methods. You can export anything – even actual DOM element ref
s:
// ChildComponent.jsx
import { forwardRef, useImperativeHandle, useRef } from "react";
export const ChildComponent = forwardRef((props, ref) => {
const privateRef = useRef();
const publicRef = {
elementRef: privateRef,
instanceMethod: () => {
// do cool stuff
},
};
useImperativeHandle(ref, () => publicRef);
return <div ref={privateRef} />;
});
To reiterate, this method probably won’t be necessary often. Usually, you can move functions up into the parent and then pass props down to child components. But I think it’s still good to know how to do it, because it can be helpful sometimes (especially to those coming from more object-oriented languages).
If you’d like to see some instance methods in action (in a component library setting), you can check out react-spring/parallax
, which is an open-source React component that I help maintain (and also where I first learned this pattern).