React Hooks Best Practices: Unlocking Efficiency and Elegance

Riya Garg
Women in Technology
3 min readJun 12, 2023

React Hooks have revolutionised the way we approach state management and side effects in React functional components. They provide a clean and concise way to organize and reuse logic, resulting in more maintainable and efficient code. In this article, we will dive into some practical examples and explore the best practices that will help you harness the full potential of React Hooks, with less theory and more actionable insights.

Use Hooks at the Top Level:

To ensure consistent behavior and prevent unexpected issues, always call Hooks at the top level of your functional component. Avoid placing them within loops, conditions, or nested functions. By following this practice, you guarantee that Hooks are executed in the same order on every render, providing predictable results.

function Counter() {
const [count, setCount] = useState(0);

useEffect(() => {
// Perform side effects here
}, [count]);

if (count === 10) {
// Avoid calling Hooks here
}

return (
// JSX code here
);
}

Optimize useEffect with Dependency Arrays:

The useEffect Hook is a powerful tool for managing side effects, but it requires careful handling of dependencies to avoid unnecessary re-renders. Provide a dependency array as the second argument to useEffect, specifying the variables that the effect depends on. This ensures that the effect only runs when the specified dependencies change.

function UserLoader({ userId }) {
useEffect(() => {
// Fetch user data based on userId
fetchUser(userId);
}, [userId]);

// ...
}

Memoize Expensive Computations:

To optimize performance, utilize the useCallback and useMemo Hooks. useCallback memoizes a function, preventing it from being recreated on each render. This is particularly useful when passing functions as props to child components. useMemo memoizes the result of expensive computations, avoiding unnecessary recalculations.

function List({ items, onItemClick }) {
const handleClick = useCallback((item) => {
onItemClick(item);
}, [onItemClick]);

const processedItems = useMemo(() => {
// Expensive computation
return processItems(items);
}, [items]);

// ...
}

Encapsulate Logic in Custom Hooks:

Custom Hooks provide a way to encapsulate and reuse logic across multiple components. By extracting shared functionality into a custom Hook, you promote code reusability and maintainability. Custom Hooks should start with the “use” prefix to indicate their Hook nature.

function useWindowDimensions() {
const [windowDimensions, setWindowDimensions] = useState({
width: window.innerWidth,
height: window.innerHeight,
});

useEffect(() => {
const handleResize = () => {
setWindowDimensions({
width: window.innerWidth,
height: window.innerHeight,
});
};

window.addEventListener('resize', handleResize);

return () => {
window.removeEventListener('resize', handleResize);
};
}, []);

return windowDimensions;
}

function MyComponent() {
const { width, height } = useWindowDimensions();

// ...
}

Use Effect Cleanup:

Leverage the useEffect hook’s cleanup function to handle resource cleanup and unsubscribe from subscriptions or event listeners. Failing to clean up can lead to memory leaks and unexpected behavior.

import React from 'react';

const Timer = () => {
const [time, setTime] = React.useState(0);

React.useEffect(() => {
const timerId = setInterval(() => {
setTime((prevTime) => prevTime + 1);
}, 1000);

return () => {
clearInterval(timerId);
};
}, []);

return <p>Time: {time} seconds</p>;
};

Separate Side Effects with useEffect:

Avoid bundling multiple unrelated side effects within a single useEffect. Instead, create separate useEffect hooks to clearly express the intention and maintain a clean and predictable code structure.

import React from 'react';

const Post = ({ postId }) => {
const [post, setPost] = React.useState(null);
const [comments, setComments] = React.useState([]);

React.useEffect(() => {
fetchPost(postId).then((data) => {
setPost(data);
});
}, [postId]);

React.useEffect(() => {
fetchComments(postId).then((data) => {
setComments(data);
});
}, [postId]);

return (
<div>
<h1>{post ? post.title : 'Loading...'}</h1>
{comments.length > 0 ? (
comments.map((comment) => (
<p key={comment.id}>{comment.text}</p>
))
) : (
<p>No comments yet.</p>
)}
</div>
);
};

Keep Hooks Focused and Simple:

To maintain code readability and modularity, it’s essential to keep Hooks focused on a single concern. Each Hook should manage a specific piece of state or handle a particular side effect. If your component requires multiple related states or side effects, consider splitting them into separate Hooks.

function usePagination(initialPage) {
const [currentPage, setCurrentPage] = useState(initialPage);
const nextPage = () => setCurrentPage((prevPage) => prevPage + 1);
const previousPage = () => setCurrentPage((prevPage) => prevPage - 1);

return { currentPage, nextPage, previousPage };
}

function MyComponent() {
const { currentPage, nextPage, previousPage } = usePagination(1);

// ...
}

Conclusion:

React Hooks have brought a paradigm shift to React development, enabling cleaner and more efficient code. By following these best practices, such as using Hooks at the top level, optimizing useEffect with dependency arrays, leveraging useCallback and useMemo for performance, encapsulating logic in custom Hooks, and keeping Hooks focused and simple, you can take full advantage of the power and elegance that Hooks offer. Embrace these practices, and elevate your React development to new heights!

References:

React Hooks Documentation: https://reactjs.org/docs/hooks-intro.html React Hooks FAQ: https://reactjs.org/docs/hooks-faq.html

--

--

Riya Garg
Women in Technology

Mentor, writer and passionate about everything web.