17 Feb 2024




Advanced

Example without using a Higher-Order Component (HOC), and then we'll introduce the HOC to demonstrate how it solves a code duplication problem.

Example 1:

In the example, we start with a simple React component named SimpleComponent that renders a message passed via props. Initially, we use this component directly in the AppWithoutHOC component.

Without HOC:

1. SimpleComponent.tsx

import React from 'react';

// Define a simple React component
const SimpleComponent = (props: { message: string }) => {
    return <div>{props.message}</div>;
};

export default SimpleComponent;

2. AppWithoutHOC.tsx

import React from 'react';
import SimpleComponent from './SimpleComponent';

// App without using HOC
const AppWithoutHOC = () => {
    return (
        <div>
            <SimpleComponent message="Without HOC: Hello, React!" />
        </div>
    );
};

export default AppWithoutHOC;

Problem Statement:

Now, imagine you want to add a common styling or behavior to multiple components, and you find yourself duplicating code across these components. This can lead to maintenance challenges and code redundancy.

Introducing HOC:

1. withEnhancement.tsx

import React from 'react';

// Define a Higher-Order Component (HOC) function
const withEnhancement = (WrappedComponent: React.ComponentType<any>) => {
    // Enhance the WrappedComponent here
    const EnhancedComponent = (props: any) => {
        // Enhance props or add additional functionality
        return (
            <div style={{ border: '2px solid blue', padding: '10px' }}>
                <WrappedComponent {...props} />
            </div>
        );
    };

    return EnhancedComponent;
};

export default withEnhancement;

2. EnhancedSimpleComponent.tsx

import React from 'react';
import SimpleComponent from './SimpleComponent';
import withEnhancement from './withEnhancement';

// Use the HOC to enhance the SimpleComponent
const EnhancedSimpleComponent = withEnhancement(SimpleComponent);

export default EnhancedSimpleComponent;

3. AppWithHOC.tsx

import React from 'react';
import EnhancedSimpleComponent from './EnhancedSimpleComponent';

// App using HOC
const AppWithHOC = () => {
    return (
        <div>
            <EnhancedSimpleComponent message="With HOC: Hello, React!" />
        </div>
    );
};

export default AppWithHOC;

Explanation:

  • Introduction of HOC:

    • We create a withEnhancement HOC that takes a WrappedComponent and adds a border and padding to it.
    • The enhanced version is returned as EnhancedComponent.
  • Usage of HOC:

    • We use the HOC to enhance the SimpleComponent and create a new component called EnhancedSimpleComponent.
    • This EnhancedSimpleComponent now has the added styling from the HOC.
  • Benefits of HOC:

    • Instead of duplicating styling or behavior in multiple components, we encapsulate it in a single HOC.
    • This promotes code reusability, reduces redundancy, and makes it easier to maintain and update shared functionality.
    • If you need to update the common styling or behavior, you can do it in one place (inside the HOC), and all components using the HOC will automatically reflect the changes.

In summary, Higher-Order Components help in avoiding code duplication and improving maintainability by encapsulating shared logic or styling in a reusable function. They provide a way to enhance components with common features without modifying their original code.

Certainly! Let's consider another example to illustrate how Higher-Order Components (HOCs) can be used to provide additional functionality to components.

Example 2: Authentication HOC

Suppose you have a React application where certain components should only be accessible to authenticated users. You want to implement a mechanism to check if a user is authenticated before rendering these components.

Without HOC:

1. HomeComponent.tsx

import React from 'react';

const HomeComponent = () => {
    return <div>Welcome to the home page!</div>;
};

export default HomeComponent;

2. UserProfileComponent.tsx

import React from 'react';

const UserProfileComponent = () => {
    return <div>User Profile: John Doe</div>;
};

export default UserProfileComponent;

3. AppWithoutHOC.tsx

import React, { useState } from 'react';
import HomeComponent from './HomeComponent';
import UserProfileComponent from './UserProfileComponent';

const AppWithoutHOC = () => {
    const [isAuthenticated, setIsAuthenticated] = useState(false);

    return (
        <div>
            {isAuthenticated ? (
                <UserProfileComponent />
            ) : (
                <HomeComponent />
            )}
        </div>
    );
};

export default AppWithoutHOC;

Problem Statement:

  • Without HOCs, you're forced to handle authentication logic inside each component that requires it.
  • This leads to code duplication and makes it harder to maintain and test.

Using HOC:

1. withAuthentication.tsx

import React, { ComponentType, useState } from 'react';

const withAuthentication = <P extends object>(WrappedComponent: ComponentType<P>) => {
    const WithAuthentication: React.FC<P> = (props) => {
        const [isAuthenticated, setIsAuthenticated] = useState(false);

        return isAuthenticated ? <WrappedComponent {...props as P} /> : <div>Not authenticated</div>;
    };

    return WithAuthentication;
};

export default withAuthentication;

2. AppWithHOC.tsx

import React from 'react';
import HomeComponent from './HomeComponent';
import UserProfileComponent from './UserProfileComponent';
import withAuthentication from './withAuthentication';

const UserProfileWithAuth = withAuthentication(UserProfileComponent);

const AppWithHOC = () => {
    return (
        <div>
            <HomeComponent />
            <UserProfileWithAuth />
        </div>
    );
};

export default AppWithHOC;

Explanation:

  • Authentication HOC:

    • The withAuthentication HOC is created to handle authentication logic.
    • It wraps the given component and conditionally renders it based on the authentication state.
  • Usage of HOC:

    • In AppWithHOC, UserProfileComponent is wrapped with withAuthentication to create UserProfileWithAuth.
    • Now, UserProfileWithAuth automatically checks if the user is authenticated before rendering the profile component.
  • Benefits of HOC:

    • With HOCs, authentication logic is centralized, promoting code reusability and maintainability.
    • Components can focus on their primary responsibility without concerning themselves with authentication details.
    • If the authentication logic needs to change, it can be updated in one place (inside the HOC) without touching individual components.

This example demonstrates how HOCs can be used to add authentication functionality to components, making the code cleaner, more modular, and easier to manage.

Example 3:

Let's consider another example where we use a Higher-Order Component (HOC) to handle loading states for asynchronous data fetching.

Without HOC:

1. UserDataComponent.tsx

import React, { useState, useEffect } from 'react';

const UserDataComponent = () => {
    const [userData, setUserData] = useState<any>(null);
    const [loading, setLoading] = useState<boolean>(true);

    useEffect(() => {
        // Simulate asynchronous data fetching
        setTimeout(() => {
            setUserData({ name: 'John Doe', age: 30 });
            setLoading(false);
        }, 2000);
    }, []);

    return (
        <div>
            {loading ? (
                <div>Loading...</div>
            ) : (
                <div>
                    <h2>User Data</h2>
                    <p>Name: {userData.name}</p>
                    <p>Age: {userData.age}</p>
                </div>
            )}
        </div>
    );
};

export default UserDataComponent;

Problem Statement:

  • The UserDataComponent handles both data fetching and loading state management.
  • This can become cumbersome and lead to code duplication if multiple components require similar behavior.

Using HOC:

1. withLoading.tsx

import React, { ComponentType, useState, useEffect } from 'react';

const withLoading = <P extends object>(WrappedComponent: ComponentType<P>) => {
    const WithLoading: React.FC<P> = (props) => {
        const [loading, setLoading] = useState<boolean>(true);

        useEffect(() => {
            // Simulate asynchronous data fetching
            setTimeout(() => {
                setLoading(false);
            }, 2000);
        }, []);

        return <WrappedComponent {...props as P} loading={loading} />;
    };

    return WithLoading;
};

export default withLoading;

2. UserDataWithLoading.tsx

import React from 'react';
import withLoading from './withLoading';

interface UserData {
    name: string;
    age: number;
}

const UserDataComponent: React.FC<{ userData: UserData; loading: boolean }> = ({ userData, loading }) => {
    return (
        <div>
            {loading ? (
                <div>Loading...</div>
            ) : (
                <div>
                    <h2>User Data</h2>
                    <p>Name: {userData.name}</p>
                    <p>Age: {userData.age}</p>
                </div>
            )}
        </div>
    );
};

export default withLoading(UserDataComponent);

Explanation:

  • Loading HOC:

    • The withLoading HOC handles loading state management.
    • It wraps the given component and provides a loading state to it.
  • Usage of HOC:

    • In UserDataWithLoading, UserDataComponent is wrapped with withLoading to create a new component.
    • Now, UserDataWithLoading automatically manages the loading state while UserDataComponent focuses only on rendering the data.
  • Benefits of HOC:

    • HOCs promote separation of concerns by isolating loading state management from the components.
    • Components become more focused and reusable since they no longer handle loading states directly.
    • If the loading logic needs to change, it can be updated in one place (inside the HOC) without impacting individual components.

This example demonstrates how HOCs can be used to encapsulate common behavior, such as loading states, across multiple components, leading to cleaner and more maintainable code.

reactjs
higher-order-component
hoc
examples