Exposing functions from React Functional Components

Jorge Sanes
Condor Labs Engineering
3 min readAug 12, 2020

--

As maintainers of Emerald-UI we’re constantly looking for ways to improve its performance and functionalities. One of the improvements we’re working on is migrating all the components from class to functional components. This makes them easier to grasp and smaller, so we deliver less bytes to the user.

Everything was going great with useState, useEffect and useRef, the usual suspects. Then we hit a roadblock. The ExpansionTableRowGroup component exposes a method that lets you, from the outside, collapse all the inner ExpansionTableRow components:

From the implementation (using Storybook in this case) you just use a ref to access the instance of the component and call the method (in the onClick prop of the Button):

And in the test you just access the instance and call the method:

That’s everything you need to know to expose a method, test it and use it in a real life situation. Now, the problem was to implement the same functionality after converting the component into a functional one. None of the usual React Hooks can help us with this task.

Enter useImperativeHandle.

First of all, let’s see how the component looks as a Functional component:

Let’s see the changes we made:

  • We changed the structure of the component from being a class component to being a funcional component.
  • Since functional components can’t expose refs like class components do, we wrapped it in a React.forwardRef function to enable this capability.
  • Then we passed the ref coming from React.forwardRef to the useImperativeHandle hook, along with a function that returns an object with the collapseAll function. This exposes everything you put into the object to outside components.

Now, for the implementation, the code remains the same!

Then we had to write the test. That’s where we needed to do extra code:

  • We first tried to use the same technique of ref={node => (groupNode = node)} that we used in the story but it didn’t expose the collapseAll method so we had to use useRef.
  • Since we can’t use useRef outside of a React component we needed to create a component inside the test which we called UsesRef and hook the ExpansionTableRowGroup component to a ref variable we created outside of the new component.
  • We mounted the UsesRef component as we normally mount every component.
  • Now, as we declared the ref variable outside of the auxiliary component, we can access it in the test and call the collapseAll function: ref.current.collapseAll();.

Conclusion

Using the useImperativeHandle hook in functional components we can expose functions that the parent of our component can call to perform actions like collapse all the inner rows, focus an element, or anything you can imagine doing inside your component.

For testing purposes we need to use useRef so we need to create a React component inside the test and assign the ref to a test level variable so we can call it afterwards and call the desired function.

I hope this article helps you in your implementation of more complicated and advanced React functional components using hooks.

Thanks for reading.

--

--