REACT PATTERN : HOC
A Higher-Order Component (HOC) in React is a pattern used to share common functionality between components without repeating code. It is a function that takes a component and returns a new component, typically adding additional properties or behavior to the original component.
How HOCs are useful:
- Code Reusability: HOCs allow you to write reusable code. You can create a HOC that adds a particular functionality and use it across multiple components.
- Abstraction: They help in abstracting shared behavior, making the components cleaner and focused on their own responsibilities.
- Composition: HOCs can be composed together to enhance components with multiple behaviors.
When to use HOCs:
- To share behavior across multiple components: When you notice that different components need to share the same logic (such as data fetching, input handling, or analytics integration), you can extract this logic into an HOC.
- To modify props: You can use HOCs to modify the props before they reach a component, adding or changing properties as needed.
- To manage state: An HOC can be used to encapsulate stateful logic, such as managing the state of a form or toggling visibility of a component.
- To access context: If you need to access context in many components, an HOC can help to inject context as props.
When not to use HOCs:
- When composition is sufficient: If a component can be enhanced by simply passing props, there’s no need to create an HOC.
- When hooks are more appropriate: With the introduction of hooks in React 16.8, many use cases for HOCs can be more cleanly addressed using hooks, such as
useState
,useEffect
, anduseContext
. - For one-off enhancements: If only a single component requires the added functionality, it may be better to enhance that component directly rather than creating an HOC.
Designing a Higher-Order Component:
Step 1: Create a Loading Component
First, we’ll need a basic loading component to display while data is being fetched.
// Loading.js
import React from 'react';
const Loading = () => <div>Loading...</div>;
export default Loading;
Step 2: Create the HOC
Next, we’ll create the HOC itself. This component will take a WrappedComponent
and return a new component that conditionally renders either the WrappedComponent
or the Loading
component based on the isLoading
prop.
import React from 'react';
import Loading from './Loading';
// This is the HOC function. It takes a component...
const withLoading = (WrappedComponent) => {
// ...and returns another component...
return class extends React.Component {
render() {
// ... that renders the Loading component if isLoading prop is true,
// otherwise it renders the WrappedComponent.
const { isLoading, ...otherProps } = this.props;
if (isLoading) {
return <Loading />;
}
return <WrappedComponent {...otherProps} />;
}
};
};
export default withLoading;
Step 3: Use the HOC
Now, let’s say we have a UserList
component that fetches and displays a list of users. We'll use our withLoading
HOC to enhance this component.
// UserList.js
import React, { Component } from 'react';
import withLoading from './withLoading';
class UserList extends Component {
render() {
const { users } = this.props;
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
}
// We wrap our component with the withLoading HOC.
export default withLoading(UserList);
Step 4: Using the Enhanced Component
Finally, we’ll use our enhanced UserList
component within a parent component, where we manage the fetching of users and the isLoading
state.
// App.js
import React, { Component } from 'react';
import UserListWithLoading from './UserList';
// This is the enhanced component
class App extends Component {
state = {
isLoading: false,
users: [],
};
componentDidMount() {
this.fetchUsers();
}
fetchUsers = async () => {
this.setState({ isLoading: true });
try {
const response = await fetch('https://jsonplaceholder.typicode.com/users');
const users = await response.json();
this.setState({ users, isLoading: false });
} catch (error) {
console.error(error);
this.setState({ isLoading: false });
}
};
render() {
const { isLoading, users } = this.state;
return (
<div>
<h1>Users</h1>
{/* We pass the isLoading state and users to the enhanced component */}
<UserListWithLoading isLoading={isLoading} users={users} />
</div>
);
}
}
export default App;
Here, App
is a component that fetches user data and stores it in its state along with an isLoading
flag. It passes both to UserListWithLoading
, which is the UserList
component wrapped with the withLoading
HOC. When isLoading
is true
, the Loading
component is displayed. Once the data is fetched, isLoading
becomes false
, and the UserList
component is rendered with the fetched data.
Explanation:
withLoading
is an HOC that enhances any component with a loading state. It takes a component as its argument and returns a new component that can render a loading indicator.- The
UserList
component is a simple component that expects an array of users and renders them. It does not concern itself with the loading state. withLoading(UserList)
enhancesUserList
by wrapping it with additional functionality to handle the loading state.- In
App
, the state is managed to keep track of the loading status and user data. This state is passed down to theUserListWithLoading
component.
This pattern is powerful because it separates the concern of fetching data and managing loading state from the presentation of the user data. This separation of concerns makes the code easier to maintain and reason about. However, with the introduction of React hooks, you might achieve the same functionality using useState
and useEffect
hooks in functional components, which could simplify the pattern even further.
Best Practices and Considerations:
- Pass Unrelated Props Through to the Wrapped Component: Make sure to pass through props that are meant for the wrapped component, so it receives the props intended for it.
- Maximize Composability: Write HOCs in a way that they can be composed together to enhance a component with multiple behaviors.
- Avoid State Duplication: Be careful not to duplicate the state between the HOC and the wrapped component.
- Convention: By convention, HOCs are prefixed with ‘with’, as in
withRouter
,withRedux
,withStyles
, etc. - Static Methods: Be aware that HOCs do not automatically copy static methods of the wrapped component. You may need to copy them over explicitly if needed.
- Refs: HOCs can make it tricky to use refs because the ref will point to the outermost container component, not the wrapped component. To get around this, you can use
React.forwardRef
in React 16.3 and above.