DEV Community

Cover image for Use forwardRef with a higher-order component
Phuoc Nguyen
Phuoc Nguyen

Posted on • Originally published at phuoc.ng

Use forwardRef with a higher-order component

Have you heard of higher-order components (HOCs)? They're an advanced technique in React that lets us reuse component logic. Basically, an HOC is a function that takes a component and adds extra functionality to it. The cool thing is, the new component can be used just like any other React component and can be combined with other components to make really complex user interfaces.

In this post, we're going to dive into using HOCs with forwardRef. This feature lets us pass refs through intermediate components to a child component. By the end of this post, you'll have a better understanding of how to use HOCs and forwardRef to write more efficient and reusable React components.

But first, let's cover the basics of HOCs.

What's a high-order component?

Higher-order components (HOCs) are a super useful tool in React for handling cross-cutting concerns like logging, authentication, or caching. By wrapping components with HOCs, we can separate concerns and keep our codebase more maintainable.

Creating an HOC in React is simple. We just define a function that takes a component as its argument and returns a new component that renders the original one with some extra props or behavior. We can then use this HOC like any other component by passing it some props.

Let's say we have a Profile component that should only be accessible to authenticated users. We could create an HOC that handles the authentication logic and wraps the Profile component. Here's an example code for such an HOC:

import { Redirect } from 'react-router-dom';

export const withAuth = (Component) => {
    const AuthenticatedComponent = (props) => {
        // Replace with your authentication logic here
        const isAuthenticated = true;
        return isAuthenticated ? <Component {...props} /> : <Redirect to="/login" />;
    };

    return AuthenticatedComponent;
};
Enter fullscreen mode Exit fullscreen mode

In this example, we have a function called withAuth that takes a component as input and returns a new component called AuthenticatedComponent. This new component checks whether the user is authenticated and redirects them to the login page if they are not. If the user is authenticated, it renders the original component with its props. Simple, right?

To redirect a user to the login page, we use the Redirect component provided by the React router package. To use this higher-order component (HOC) with our Profile component, we just need to wrap it like this:

import { withAuth } from './withAuth';

const Profile = () => {
    return (
        <div>This is the profile page</div>
    );
};

export const withAuth(Profile);
Enter fullscreen mode Exit fullscreen mode

From now on, if an unauthenticated user attempts to access the /profile route, they will be automatically redirected to the login page.

Enhancing React components with high-order components and forwardRef

If you're looking to take your React components to the next level, consider using a high-order component (HOC) with forwardRef. This technique allows you to add extra functionality to your components by passing in additional props and manipulating their behavior based on those props. The result is more reusable and maintainable code that promotes better separation of concerns and a cleaner, more organized codebase.

Using a high-order function with forwardRef provides several benefits to your React application. For one, it allows you to create more modular, extensible components that can be easily manipulated based on the props passed into them. This makes your code more reusable and easier to maintain over time.

In addition, HOCs enable better separation of concerns by isolating specific functionalities that can be reused across multiple components in your application. This helps reduce code duplication and improves the overall organization of your codebase.

Finally, using HOCs promotes a cleaner and more organized codebase by separating the logic from the presentation layer. This lets you focus on building UI components without worrying about the underlying business logic.

To see how this works in practice, let's take a look at an example. Say you have a functional component called Button that renders a button with some text, and has a prop to handle the click event. Here's a minimal implementation of that Button:

export const Button = ({ onClick, children }, ref) => {
    return (
        <button ref={ref} onClick={onClick}>
            {children}
        </button>
    );
};
Enter fullscreen mode Exit fullscreen mode

Let's say you want to give your users feedback that something is happening when they click a button. One way to achieve this is by adding a loading indicator to your Button component. Fortunately, you can use a higher-order component with forwardRef to add this functionality without modifying the existing code of your Button.

Here's an example of how you can easily achieve this functionality:

import { Button } from './Button';

const withLoading = (Component) => {
    const WrappedComponent = ({ isLoading, ...props }, ref) => {
        return isLoading ? (
            <div className="loading">Loading...</div>
        ) : (
            <Component {...props} ref={ref} />
        );
    };

    return React.forwardRef(WrappedComponent);
};

export const withLoading(Button);
Enter fullscreen mode Exit fullscreen mode

In this code snippet, we're defining a new HOC (higher-order component) called withLoading. It takes a component as input and returns a new component that either renders the original component or a loading indicator based on the value of the isLoading prop.

The WrappedComponent function receives the isLoading prop, along with all other props passed down to it. If isLoading is true, it renders the loading indicator. Otherwise, it renders the original component with its props.

To use this HOC with our Button component, we simply wrap it like this:

import { ButtonWithLoading } from './ButtonWithLoading';

const App = () => {
    const [isLoading, setIsLoading] = useState(false);

    const handleClick = () => {
        setIsLoading(true);
        // Perform some async operation here
        fetch('/api/do/some/thing').then(() => {
            setIsLoading(false);
        });
    };

    return (
        <ButtonWithLoading onClick={handleClick} isLoading={isLoading}>
            Click me
        </ButtonWithLoading>
    );
};
Enter fullscreen mode Exit fullscreen mode

When the Button is clicked, the isLoading prop turns to true and shows a loading indicator. Once the data is fetched from the back-end (which simulates an async operation), isLoading goes back to false and the original Button component is displayed again.

Using a high-order function with forwardRef is an excellent way to improve your component's functionality, reusability, maintainability, and organization. This approach lets you add custom functionality without altering or even seeing how it's used in the components that utilize this high-order function.

Conclusion

In conclusion, using higher-order components (HOCs) with forwardRef is an advanced technique that can significantly enhance the functionality, reusability, maintainability, and organization of your React codebase. By breaking down complex functionalities into smaller, reusable components, you can create more modular and flexible components that can be easily customized based on the props passed into them.

This approach allows you to focus solely on building UI components without worrying about the underlying business logic. So, the next time you're working on a React project, consider using HOCs with forwardRef to take your components to the next level!


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)