React Design Pattern Part 2: HOC

Md Sajjad Hosen Noyon

15 August, 2024

We often want to use the same logic in multiple components within our application, so we reduce code duplication as much as possible. One way to do this and make code logic reusable is using one of the React design patterns, the Higher Order Component(HOC).

One of the empowering features of an HOC is its flexibility. Its function can take a component and return a new component with additional or extended functionality, allowing us to modify our components according to our needs.

When to use it
  • Need to share logic between multiple components without duplicating code.
  • To add common behaviors or features to various components.
  • Need to isolate presentation logic from business logic in a component.
When not to use it
  • When you have logic specific to a single component.
  • When the logic is too complex, it may make understanding HOCs challenging.
Advantages
  • Promotes code reuse by encapsulating and sharing logic between components.
  • HOCs allow a clear separation of presentation and business logic, bringing clarity and efficiency to our code.
  • Applying functional design patterns facilitates code composition(combining smaller, independent components to create complex UIs) and modularity.
Disadvantages
  • It may introduce an additional layer of abstraction that makes it difficult to track data flow.
  • Excessive composition of HOCs can generate complex components that are difficult to debug.
  • Sometimes, it can hide the component hierarchy, making an understanding of how the application is structured.
Example 1

In the below example, there are two counters: ClickCounter and HoverCounter. The two components have the same logic for counting their values by 1. So, WithCounter(HOC) contains a component as a parameter with the logic of both counter and return a new component.

				
					const ClickCounter = ({ count, incrementCount }) => {
return (
 <div>
 <button type="button" onClick={incrementCount}>
  Clicked {count} times.
 </button>
 </div>
);
}

export default withCounter(ClickCounter);
				
			

The above component is ClickCounter, which takes props with counter state and counter handler from WithCounter. With every click, the counter value increases by 1.

				
					const HoverCounter = ({ count, incrementCount }) => {
const { count, incrementCount } = props;

return (
 <div>
 <button onMouseOver={incrementCount}>
  Hoverd {count} times.
 </button>
 </div>
);
}

export default withCounter(HoverCounter);
				
			

The above component is HoverCounter, which takes props with a counter state and counter handler from withCounter. In every hover, the counter value increases by 1.

				
					const withCounter = (OriginalComponent) => {
 function NewCounter(props){
  const [count, setCount] = useState(0);

  const incrementCount = () => {
   setCount(prev => prev + 1);
  }

  return (
    <OriginalComponent
      {...props}
      count={count}
      incrementCount={incrementCount}
    />
  );
 };

 return NewCounter;
}
				
			

The above function is withCounter, our HOC that takes a component named OriginalComponent as a parameter. An inner function has the main logic, and the handler returns as a prop with OriginalComopnent. Then, WithCounter returns its inner function component.

Example 2

We want to create a HOC that handles the state of the data and the methods for submitting it from a form. The HOC will handle the form values, validate the data, and send the request to the server.

				
					// HOC that handles form state and logic
function withForm({ OriginalComponent }) {
 const NewForm = (props) => {
   const [formValues, setFormValues] = useState({});

   const handleInputChange = event => {
     const { name, value } = event.target;
     setFormValues(prevValues => ({
       ...prevValues,
       [name]: value,
     }));
   };

   const handleSubmit = event => {
     event.preventDefault();
     props.onSubmit(formValues);
   };

   return (
     <OriginalComponent
       {...props}
       formValues={formValues}
       onInputChange={handleInputChange}
       onSubmit={handleSubmit}
     />
   );
 };

 return NewForm;
}


const LoginForm = ({ formValues, onInputChange, onSubmit }) => {
 return (
   <form onSubmit={onSubmit}>
     <input
         type="text"
         name="name"
         value={formValues.name || ''}
         onChange={onInputChange}
      />
      <input
         type="text"
         name="email"
         value={formValues.email || ''}
         onChange={onInputChange}
       />
       <button type="submit">Enviar</button>
   </form>
 );
};

// Using the HOC to wrap the MyForm component
const LoginFormWithLogic = withForm(MyForm);


// Main component that renders the form
const App: React.FC = () => {
 const handleSubmit = (values: FormValues) => {
   console.log('Form values:', values);
   // Logic to send the form data to the server
 };

 return (
   <div>
     <h1>HOC Form</h1>
     <FormWithLogic onSubmit={handleSubmit} />
   </div>
 );
};

export default App;
				
			
Naming Conventions
  • We have seen many examples of higher-order components in this blog post. You might have noticed the HOCs being with the word with.
  • withForm, withCounter, withErrorBoundary, withTranslation, etc. Higher-order components do not have strict naming conventions, but it is a good practice to prefix the HOC with the keyword with.
  • We should include the name of the HOC in the new component we create after wrapping it to indicate that this component uses an HOC.

 

For example, we had created a HOC withTranslation and a component called the LoginComponent and our enhanced component was called LoginComponentWithTranslation.

				
					const LoginComponentWithTranslation = withTranslation(LoginComponent, i18n);
				
			
Common use cases of HOC
  • Management of state and handling of async data
  • Conditional rendering: Authorization and feature toggle
  • Translation and language switching
  • Error Boundary
 

Note: For more details, see references.

Conclusion

In this blog post, we have learned what higher-order components are and how to create simple higher-order components.

We have also observed scenarios where developers commonly use higher-order components.

This post will give you a good understanding of how and where to use higher-order components in your application.

Reference
Md Sajjad Hosen Noyon

15 August, 2024