Use forwardRef with a higher-order component
Tags: react, tutorial, javascript, webdev
Published: 2023-12-12 14:46:20

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:

```jsx
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;
};
```

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](https://reactrouter.com/) package. To use this higher-order component (HOC) with our `Profile` component, we just need to wrap it like this:

```jsx
import { withAuth } from './withAuth';

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

export const withAuth(Profile);
```

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`:

```jsx
export const Button = ({ onClick, children }, ref) => {
    return (
        <button ref={ref} onClick={onClick}>
            {children}
        </button>
    );
};
```

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:

```jsx
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);
```

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:

```jsx
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>
    );
};
```

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](https://github.com/phuocng/master-of-react-ref) 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:

-   [Twitter](https://twitter.com/_phuocng)
-   [GitHub](https://github.com/phuocng)