In modern frontend development, especially with frameworks like React, managing reusable logic efficiently is crucial for maintaining clean and scalable code. While Higher-Order Components (HOCs) were a popular pattern for sharing logic, there are now several alternatives that can help achieve the same goal without the complexities that HOCs can introduce. Below, I will discuss several strategies for handling reusable logic, including custom hooks, render props, and context API, along with practical examples and best practices.
Custom hooks are a powerful way to encapsulate reusable logic in React. They allow you to extract component logic into reusable functions. A custom hook is simply a JavaScript function whose name starts with "use" and can call other hooks.
import { useState, useEffect } from 'react';
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch(url);
if (!response.ok) throw new Error('Network response was not ok');
const result = await response.json();
setData(result);
} catch (error) {
setError(error);
} finally {
setLoading(false);
}
};
fetchData();
}, [url]);
return { data, loading, error };
}
This custom hook, `useFetch`, can be reused across different components to fetch data from any URL, simplifying the component code and promoting reusability.
The render props pattern involves passing a function as a prop to a component, allowing you to share code between components. This pattern can be particularly useful for sharing logic that requires rendering.
const DataProvider = ({ render }) => {
const { data, loading, error } = useFetch('https://api.example.com/data');
return render({ data, loading, error });
};
// Usage
{
if (loading) return Loading...
;
if (error) return Error: {error.message}
;
return {JSON.stringify(data)};
}} />
This approach allows for a flexible way to handle data fetching while keeping the logic encapsulated within the `DataProvider` component.
The Context API is another powerful tool for sharing state and logic across components without prop drilling. It can be used to create a global state or to provide shared functionality.
const DataContext = React.createContext();
const DataProvider = ({ children }) => {
const { data, loading, error } = useFetch('https://api.example.com/data');
return (
{children}
);
};
// Usage
const DataConsumer = () => {
const { data, loading, error } = useContext(DataContext);
if (loading) return Loading...
;
if (error) return Error: {error.message}
;
return {JSON.stringify(data)};
};
This method allows any component within the `DataProvider` to access the fetched data without needing to pass props explicitly.
By leveraging these patterns, developers can effectively manage reusable logic in their applications, leading to cleaner, more maintainable codebases.