Those React Hooks Are Essential for Frontend Developers
Discover how these game-changing hooks can streamline your code, boost performance, and make your frontend development more efficient than ever.
Hooks are an essential part of building React apps, yet I often hear questions about which ones are truly essential for frontend developers. While the best answer might be “the more, the better,” I’ve narrowed it down to the must-know hooks.
Here’s a list of 5 crucial React hooks that every developer should master to confidently build apps.
useState
The useState hook is the foundation of state management in React functional components. With useState, you can store values like numbers, strings, objects, or arrays and dynamically update them as your component evolves.
Whether you’re toggling a theme, tracking user input, or counting clicks, useState is your go-to hook for managing simple states in React.
const [state, setState] = useState(initialState)Here’s a simple example of using useState to create a counter:
import React, {useState} from "react";
export default function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}1. Initial State: useState(0) initializes the count state with a value of 0.
2. Accessing State: The current value of count is displayed in the <p> tag.
3. Updating State: The setCount function is called inside the onClick event to increment the count by 1. React then re-renders the component to reflect the updated value.
useEffect
The useEffect hook is used to perform side effects in your React functional components. It allows you to run code after the component renders, such as fetching data, updating the DOM, or setting up subscriptions. useEffect can replace lifecycle methods like componentDidMount, componentDidUpdate, and componentWillUnmount from class components.
By using useEffect, you can ensure your component behaves as expected during different stages of its lifecycle.
useEffect(setup, dependencies?)Here’s an example that fetches data from an API when the component mounts:
import React, {useEffect, useState} from "react";
export default function DataFetcher() {
const [data, setData] = useState(null);
useEffect(() => {
fetch("https://api.example.com/data")
.then((response) => response.json())
.then((data) => setData(data))
}, []);
return (
<div>
{
data
? <p>{JSON.stringify(data)}</p>
: <p>Loading...</p>
}
</div>
);
}1. Effect Function: The useEffect hook here fetches data from the API. It runs the code inside the function when the component is mounted.
2. Dependency Array: The empty dependency array [] ensures that the effect only runs once, when the component first mounts.
3. State Update: Once the data is fetched, it updates the data state with the response. React re-renders the component, displaying the fetched data once it’s available.
useCallback
The useCallback hook is used to memoize functions in React, which helps optimize performance by preventing unnecessary re-creations of functions during re-renders. It returns a memoized version of the callback function that only changes if one of its dependencies has changed.
This is especially useful when passing callbacks to child components or in scenarios where functions are used as dependencies in other hooks (like useEffect or useMemo).
const cachedFn = useCallback(fn, dependencies)Here’s an example that shows how useCallback can optimize a function that’s passed as a prop:
import React, { useState, useCallback } from "react";
export default function Parent() {
const [count, setCount] = useState(0);
const increment = useCallback(() => {
setCount(count + 1);
}, [count]);
return <Child increment={increment} />;
}
function Child({ increment }) {
console.log("Child rendered");
return (
<div>
<button onClick={increment}>Increment</button>
</div>
);
}1. Memorizing the Function: In this example, increment is memoized using useCallback. This ensures that the function is only recreated when the count state changes, avoiding unnecessary re-creations during re-renders.
2. Passing Memorized Function: The increment function is passed down to the Child component as a prop. Without useCallback, the function would be re-created every time the Parent component re-renders, causing unnecessary renders of the Child component.
3. Optimization: useCallback prevents the Child component from re-rendering unnecessarily when the function reference remains the same, improving performance.
useRef
The useRef hook is used to persist values across renders without causing a re-render itself. It can be used to access and interact with DOM elements directly or to store mutable values that don’t require a re-render when updated.
Unlike state, updates to useRef do not trigger a component re-render, making it ideal for situations where you need to track values or interact with DOM nodes without affecting performance.
const ref = useRef(initialValue)Here’s an example where useRef is used to access a DOM element and focus an input when a button is clicked:
import React, { useRef } from "react";
export default function FocusInput() {
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current.focus();
};
return (
<div>
<input ref={inputRef} type="text" placeholder="" />
<button onClick={focusInput}>Focus Input</button>
</div>
);
}1. Creating a Ref: inputRef is created using useRef(null). This gives us a reference object that will be used to store a reference to the <input> element.
2. Accessing the DOM Element: The inputRef.current property holds the DOM element itself (in this case, the input element). By passing inputRef to the ref attribute of the input, React automatically assigns the DOM element to inputRef.current once the component is mounted.
3. Interacting with the DOM: When you call inputRef.current.focus(), you are not updating inputRef.current; you are simply calling the focus() method on the DOM element it references. This doesn’t cause any state changes or re-renders because inputRef.current is a reference to a DOM node, not part of React’s state management.
useMemo
The useMemo hook is used to memoize the result of a calculation, preventing it from being recomputed on every render unless its dependencies change. This is useful for optimizing performance, especially in components that involve expensive calculations or operations.
By memorizing the result, React can skip re-running the function and reuse the previously computed value if the dependencies haven’t changed, making the component more efficient.
const cachedValue = useMemo(calculateValue, dependencies)Imagine you have a list of items and you need to filter them based on a search term. Without useMemo, the filtering operation would be recalculated every time the component re-renders, which can become inefficient for large lists.
import React, {useMemo, useState} from "react";
export default function ItemList() {
const [searchTerm, setSearchTerm] = useState("");
const [items] = useState([
"Apple", "Banana", "Orange", "Strawberry", "Pineapple"
]);
const filteredItems = useMemo(() => {
return items.filter((item) => {
item.toLowerCase().includes(searchTerm.toLowerCase())
});
}, [searchTerm, items]);
return (
<div>
<input type="text"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}/>
<ul>
{
filteredItems.map((item, index) => (
<li key={index}>{item}</li>
))
}
</ul>
</div>
);
}1. Large List and Search Term: In this example, items is a large list of strings, and searchTerm is the value entered by the user to filter the list.
2. Filtering the List: The filteredItems array contains the items that match the search term. Without useMemo, every time the component re-renders (even if just a small part of the UI changes), React would recalculate the filtered list, which can be inefficient with large datasets.
3. Memoizing the Filtered List: useMemo is used to memoize the filtered list so that it only recalculates when either searchTerm or items changes. This avoids unnecessary filtering on every render and improves performance.
Best Practices
• Keep hooks simple and focused. Each hook should serve one clear purpose. Avoid overcomplicating your components by combining too many hooks that do different things.
• Optimize performance selectively. While hooks like useMemo and useCallback are powerful for performance optimization, use them sparingly. Overuse can lead to unnecessary complexity without real performance gains.
• Understand the dependency array. With useEffect, useMemo, and useCallback, be mindful of the dependencies you provide in the dependency array. Incorrect dependencies can lead to bugs or inefficient renders.
Summary
By understanding these hooks and applying them strategically, you can significantly improve the performance and clarity of your React components.
Whether you’re building small components or large applications, mastering hooks will make your code more maintainable, efficient, and easier to reason about.



Great post! Super useful