React 18 has supercharged the way we build functional components. Hooks are a game-changer, letting us write cleaner, more reusable, and easier-to-understand React components. In this blog, we’ll dive into 15 essential hooks, from the basics to advanced techniques, and see how they can elevate your React projects.
1. useState
The useState hook allows you to add state variables to functional components. State represents dynamic data, which can change over time based on user interactions or other factors.
Use Case: Managing simple state like form inputs, counters, toggles, etc.
Example: Counter
function Counter() {
const [count, setCount] = useState(0); // Initial count is set to 0
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
Here, clicking the “Increment” button updates count, and useState re-renders the component whenever count changes.
2. useEffect
useEffect lets you perform side effects in functional components, like data fetching, setting up subscriptions, or manually modifying the DOM. By default, useEffect runs after every render, but it can be configured to run conditionally.
Use Case: Fetching data on component mount, setting up event listeners, or updating the document title.
Example: Fetching Data
function DataFetcher() {
const [data, setData] = useState(null);
useEffect(() => {
fetch(‘https://api.example.com/data‘)
.then(response => response.json())
.then(setData);
}, []); // Empty dependency array: runs once on mount
return <div>{data ? JSON.stringify(data) : ‘Loading…‘}</div>;
}
Here, the useEffect runs once after the component mounts (thanks to the empty dependency array), fetching data and storing it in data.
3. useContext
useContext lets you access global data (like themes or user authentication) that would otherwise have to be passed through many levels of components as props.
Use Case: Accessing global data, such as themes, languages, or authentication status, without prop drilling.
Example: Theme Context
const ThemeContext = createContext(‘light‘);
function ThemedComponent() {
const theme = useContext(ThemeContext);
return <div>Current theme: {theme}</div>;
}
By using useContext, you can easily access ThemeContext in any component without needing to pass theme as a prop through multiple layers.
4. useReducer
useReducer is an alternative to useState for managing more complex state logic. It works similarly to Redux and is especially helpful for managing states that rely on multiple transitions or involve complex data updates.
Use Case: Managing state with complex logic, like form handling or multi-step workflows.
Example: Counter with Reducer
function reducer(state, action) {
switch (action.type) {
case ‘increment‘:
return { count: state.count + 1 };
case ‘decrement‘:
return { count: state.count – 1 };
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: ‘increment‘ })}>+</button>
<button onClick={() => dispatch({ type: ‘decrement‘ })}>–</button>
</div>
);
}
useReducer is ideal here because it separates action types and transitions, making it easy to extend with new actions without cluttering the code.
5. useRef
useRef is a versatile hook that provides a way to store mutable values without causing re-renders. It’s commonly used to access and manipulate DOM elements directly.
Use Case: Storing references to DOM elements, managing timers, or holding previous state values.
Example: Focusing an Input Field
function FocusInput() {
const inputRef = useRef();
return (
<div>
<input ref={inputRef} />
<button onClick={() => inputRef.current.focus()}>Focus</button>
</div>
);
}
Here, inputRef.current.focus() directly manipulates the input element, focusing it without a re-render.
6. useMemo
useMemo is used to optimize performance by memoizing the result of expensive calculations between renders. It only recalculates when its dependencies change.
Use Case: Improving performance for costly calculations or derived values that don’t need recalculating each render.
Example: Expensive Calculation
function ExpensiveCalculation({ value }) {
const result = useMemo(() => {
return performExpensiveCalculation(value);
}, [value]);
return <div>Result: {result}</div>;
}
By memoizing performExpensiveCalculation, React only recalculates when value changes, which can significantly improve performance.
7. useCallback
useCallback is similar to useMemo but memoizes functions. It’s useful for passing stable references of callback functions, especially to child components that rely on shallow comparison.
Use Case: Preventing unnecessary re-renders when passing functions as props to child components.
Example: Passing Stable Callback
function Parent() {
const [count, setCount] = useState(0);
const increment = useCallback(() => {
setCount(c => c + 1);
}, []);
return <Child increment={increment} />;
}
function Child({ increment }) {
return <button onClick={increment}>Increment</button>;
}
Using useCallback ensures that increment retains the same reference between renders, avoiding unnecessary re-renders of Child.
8. useLayoutEffect
useLayoutEffect is like useEffect but runs synchronously after all DOM mutations. It’s useful when you need to read layout information and make immediate updates to prevent visual flickers.
Use Case: Manipulating the DOM immediately after render to avoid flickering or incorrect initial layout.
Example: Adjusting Layout
function ResizableBox() {
const boxRef = useRef();
useLayoutEffect(() => {
const box = boxRef.current;
box.style.width = ‘200px‘;
box.style.height = ‘200px‘;
}, []);
return <div ref={boxRef} style={{ backgroundColor: ‘lightblue‘ }} />;
}
useLayoutEffect runs before the component is painted to the screen, ensuring layout adjustments happen immediately.
9. useImperativeHandle
useImperativeHandle customizes the ref value exposed to a parent component when using forwardRef. This allows you to limit what actions the parent can perform on a child component.
Use Case: Exposing specific methods or properties to parent components.
Example: Custom Input with Focus Control
const CustomInput = forwardRef((props, ref) => {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} />;
});
function Parent() {
const ref = useRef();
return (
<div>
<CustomInput ref={ref} />
<button onClick={() => ref.current.focus()}>Focus Input</button>
</div>
);
}
With useImperativeHandle, you expose only specific methods like focus, providing better control to the parent.
10. useDebugValue
useDebugValue helps in adding custom labels for your custom hooks to make debugging easier in React DevTools.
Use Case: Debugging custom hooks more effectively.
Example: Debugging a Custom Hook
function useCustomHook(value) {
useDebugValue(value > 10 ? ‘High‘ : ‘Low‘);
return value;
}
function Component() {
const value = useCustomHook(15);
return <div>Value: {value}</div>;
}
In React DevTools, you’ll see a label “High” or “Low” based on the hook’s state, making it clearer when debugging.
11. useId
useId generates unique IDs that are stable across the client and server. This is especially helpful when creating accessible components that require unique identifiers (e.g., for aria labels or htmlFor attributes), without worrying about ID collisions.
Use Case: Associating labels with form elements or ARIA attributes in a way that’s unique across all instances of a component.
Example: Accessible Form Fields
function FormField() {
const id = useId(); // Generates a unique ID for this component instance
return (
<div>
<label htmlFor={id}>Username</label>
<input id={id} type=“text” />
</div>
);
}
By using useId, every FormField instance will have a unique id, which is essential for accessibility and avoids conflicts when rendering multiple instances of the same component.
12. useTransition
useTransition provides a way to prioritize rendering so that the UI remains responsive during updates that may take longer. It allows you to mark state updates as “transitions” to avoid blocking the main thread and keep the UI interactive.
Use Case: When triggering an update that could delay or “freeze” the UI, such as filtering or sorting a large data set.
Example: Filtering a Large List
function SearchableList({ items }) {
const [query, setQuery] = useState(”);
const [isPending, startTransition] = useTransition();
const handleSearch = (e) => {
const value = e.target.value;
setQuery(value);
startTransition(() => {
// Expensive filtering operation marked as a transition
const filteredItems = items.filter(item => item.includes(value));
setFilteredItems(filteredItems);
});
};
return (
<div>
<input type=“text” value={query} onChange={handleSearch} placeholder=“Search…” />
{isPending ? <p>Loading…</p> : <List items={filteredItems} />}
</div>
);
}
Here, useTransition ensures that the UI remains responsive by deferring the expensive filtering calculation until it has time to complete. While the filtering occurs, isPending will be true, showing a “Loading…” message.
13. useDeferredValue
useDeferredValue lets you delay updates to a value until the main work is done, helping to prevent UI flickering and maintain smooth rendering for low-priority updates.
Use Case: Displaying non-urgent changes, such as search suggestions, after critical UI tasks are completed.
Example: Deferred Search Suggestions
function SearchInput() {
const [input, setInput] = useState(”);
const deferredInput = useDeferredValue(input); // Defers this value update
return (
<div>
<input
type=“text”
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder=“Type to search…”
/>
<SearchSuggestions query={deferredInput} />
</div>
);
}
In this example, SearchSuggestions only receives deferredInput, which will lag slightly behind input. This way, SearchSuggestions will update only after more immediate tasks are complete, reducing flickering as the user types quickly.
14. useSyncExternalStore
useSyncExternalStore ensures that React components stay synchronized with external sources of data (such as global state managers or APIs) and handle external updates correctly in concurrent rendering.
Use Case: Integrating with third-party data sources, libraries, or global stores, where changes need to reflect immediately in the component.
Example: Integrating with a Global Store
import { subscribe, getState } from ‘./globalStore‘;
function StoreSubscriber() {
// Uses the global store’s subscribe and getState functions
const state = useSyncExternalStore(subscribe, getState);
return <div>Current Store State: {JSON.stringify(state)}</div>;
}
Here, useSyncExternalStore allows StoreSubscriber to stay in sync with the global store without additional state management code, even in React’s concurrent mode.
15. useInsertionEffect
useInsertionEffect is a low-level hook for injecting styles into the DOM. It runs before DOM mutations, making it ideal for dynamically injecting styles in libraries where critical CSS rendering order matters.
Use Case: Adding styles in libraries or handling third-party CSS where style injection order is critical.
Example: Injecting Styles Dynamically
function DynamicStyleComponent() {
useInsertionEffect(() => {
const style = document.createElement(‘style‘);
style.textContent = ‘.dynamic { color: blue; }‘;
document.head.appendChild(style);
return () => {
document.head.removeChild(style);
};
}, []);
return <div className=“dynamic”>Styled Text</div>;
}
This hook ensures that the .dynamic style is applied immediately before the DOM is rendered, avoiding flickering or style conflicts. It’s particularly valuable in complex styling scenarios where style order matters.
Conclusion
React 18 has introduced a new era of functional component development. Hooks provide a flexible and efficient way to manage state, side effects, and other complex behaviour’s within your components.
Each of these React hooks serves a unique purpose that helps you tackle specific challenges in building modern, efficient, and user-friendly applications. By understanding when and why to use them, you can unlock powerful functionality within your components, handle asynchronous rendering smoothly, and maintain a responsive and accessible UI.
If you know any such example to leverage the hooks effectively please share in the comments below. Your insights will be valuable to other developers.
Happy coding! 🚀