DEV Community

Cover image for Expose methods of a component using useImperativeHandle()
Phuoc Nguyen
Phuoc Nguyen

Posted on • Originally published at phuoc.ng

Expose methods of a component using useImperativeHandle()

Sometimes, we may want to give the parent component more control over the child component by allowing access to specific methods or properties. The useImperativeHandle() hook, used in combination with forwardRef(), can help us achieve this.

With useImperativeHandle(), we can define a set of functions and values that will be accessible on the ref object from outside of the component. This is especially useful when we need to call a method on a child component from its parent, such as when triggering an animation or fetching new data.

Let's say we have a Slider component, and we want to expose some methods that allow users to navigate to the previous or next item, or jump to any item. We can use useImperativeHandle() to make this happen.

Introducing the Slider component

Let's keep things simple and work with the Slider component we created earlier. Just to refresh your memory, here's how it works:

In this example, the Slider component includes arrows on the sides for users to navigate to the previous and next items. There are also dots at the bottom to allow users to jump to any item they want.

To better understand the power of useImperativeHandle, we've removed the animation of the current dot item, leaving the Slider component with basic functionalities. However, it still includes everything for navigation. What if we exposed a few navigation methods, so users have full control of the navigation UI but still have the full advantage of the slider, such as smooth animation when navigating between items?

For example, imagine a custom slider component with several methods for controlling navigation. These methods need to be accessible to the parent component so that it can control the slider navigation.

Let's dive into the next section to get a better understanding of the basics behind useImperativeHandle().

Understanding the syntax of useImperativeHandle

The useImperativeHandle() hook is a powerful tool that takes two arguments. The first argument is the ref object we want to assign our functions and values to, while the second argument is a function that returns an object containing those functions and values.

Here's an example of how we can use useImperativeHandle() to expose methods of the Slider component:

interface SliderMethods {
    goToPreviousItem: () => void;
    goToNextItem: () => void;
    jump: (index: number) => void;
};

const Slider = React.forwardRef<SliderMethods>((props, ref) => {
    const goToPreviousItem = () => {
        ...
    };

    const goToNextItem = () => {
        ...
    };

    const jump = (index) => {
        ...
    };

    React.useImperativeHandle(ref, () => ({
        goToPreviousItem,
        goToNextItem,
        jump,
    }));

    return (
        // Render slider UI ...
    );
});
Enter fullscreen mode Exit fullscreen mode

In this example, we create an interface called SliderMethods that lists all the necessary methods for slider navigation. These methods include goToPreviousItem(), goToNextItem(), and jumpToItem(). We then pass this interface as the first argument to React.forwardRef.

To make these methods accessible to the parent component, we use useImperativeHandle. This function takes a second argument, which is a function that returns an object with all the necessary methods.

Now, these methods can be accessed on the ref object in the parent component. It's like giving the parent component a key to unlock the methods it needs to control the slider.

Using the Slider component's exposed methods

How can we use the navigation methods that are provided by the Slider component? Well, it's actually quite simple, just like using a regular ref.

To start, we need to create a reference to the Slider component using useRef(), and then attach it to the ref attribute of the corresponding element. Here's how to do it:

const sliderRef = React.useRef();

<Slider ref={sliderRef}>
    ...
</Slider>
Enter fullscreen mode Exit fullscreen mode

Calling the Slider component methods is a breeze – simply access it through the current property of the ref.

const handleClickNextButton = () => {
    const slider = sliderRef.current;
    if (slider) {
        slider.goToNextItem();
    }
};

<div className="container__next" onClick={handleClickNextButton}></div>
Enter fullscreen mode Exit fullscreen mode

When the user clicks the Next button in this example, it triggers the handleClickNextButton function. This function retrieves the reference to the Slider component from sliderRef.current. If the reference exists, it calls the goToNextItem() method, which navigates to the next item in the slider.

Check out the final demo below! You can navigate between slider items by clicking either the arrows or the dots.

Conclusion

By using useImperativeHandle, we can create a well-defined and clean API for our component. This API allows users to control specific functionalities while still maintaining internal state and functionality. This way, we can ensure that the parent component doesn't accidentally modify any internal state or call any unnecessary functions.

In summary, useImperativeHandle gives us control over which methods or values we want to expose to the parent component. This makes our components more composable and easier to use in different contexts. It's a powerful tool that should be used with caution, but it can greatly improve the user experience of our applications.


It's highly recommended that you visit the original post to play with the interactive demos.

If you found this series helpful, please consider giving the repository a star on GitHub or sharing the post on your favorite social networks 😍. Your support would mean a lot to me!

If you want more helpful content like this, feel free to follow me:

Top comments (0)