So far we’ve learnt the basic of Hooks, useState, useEffect, useRef in React, In post-5, we learnt about custom hooks. As your React apps grow, performance can suffer due to unnecessary re-renders and expensive computations. In this tutorial, we’ll introduce useMemo and useCallback, two powerful hooks for optimization. We’ll add a search feature to our Task Tracker App and use memoization to keep it fast and responsive.
If you have’t explored the previous blogs, scroll down to the bottom to follow along.
Let’s dive in!
Why Do We Need useMemo and useCallback?
As your app grows, re-renders can become costly:
- Filtering large lists every time can slow things down.
- Passing inline functions as props can cause unnecessary re-renders.
That’s where useMemo and useCallback come in:
useMemo→ memoizes (caches) the result of an expensive calculation.useCallback→ memoizes a function so it doesn’t get recreated on every render.
What You’ll Learn in This Post
- When (and when not) to use them
- How to add a search bar in the Task Tracker App
- How to optimize filtering with
useMemo - How to optimize callbacks with
useCallback
Example: Expensive Calculation with useMemo
import React, { useState, useMemo } from "react";
function ExpensiveComponent({ num }) {
const squared = useMemo(() => {
console.log("Calculating...");
return num * num;
}, [num]); // only recalculates when num changes
return <p>Square: {squared}</p>;
}
export default ExpensiveComponent;
Without useMemo, the calculation runs every render.
With useMemo, it only runs when num changes.
Example: Prevent Re-Creation of Functions with useCallback
import React, { useState, useCallback } from "react";
function Counter() {
const [count, setCount] = useState(0);
const increment = useCallback(() => setCount((c) => c + 1), []);
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
</div>
);
}
export default Counter;
Without useCallback, increment is re-created on every render.
With useCallback, React reuses the same function reference.
Mini Project Step: Add Search to Task Tracker
Let’s add a search bar that filters tasks.
Updated App.jsx
import React, { useState, useEffect, useRef, useMemo, useCallback } from "react";
import useLocalStorage from "./hooks/useLocalStorage";
function App() {
const [tasks, setTasks] = useLocalStorage("tasks", []);
const [newTask, setNewTask] = useState("");
const [search, setSearch] = useState("");
const inputRef = useRef(null);
// Fetch initial tasks only if localStorage is empty
useEffect(() => {
if (tasks.length === 0) {
fetch("https://jsonplaceholder.typicode.com/todos?_limit=5")
.then((res) => res.json())
.then((data) => setTasks(data))
.catch((err) => console.error("Error fetching tasks:", err));
}
}, []); // run once
// Auto-focus input
useEffect(() => {
inputRef.current.focus();
}, []);
const addTask = useCallback((e) => {
e.preventDefault();
if (!newTask.trim()) return;
const task = { id: Date.now(), title: newTask };
setTasks([...tasks, task]);
setNewTask("");
inputRef.current.focus();
}, [newTask, tasks, setTasks]);
// Filter tasks with useMemo
const filteredTasks = useMemo(() => {
return tasks.filter((task) =>
(task.title || task.text).toLowerCase().includes(search.toLowerCase())
);
}, [tasks, search]);
return (
<div style={{ padding: "20px" }}>
<h1>Task Tracker</h1>
{/* Search bar */}
<input
type="text"
placeholder="Search tasks..."
value={search}
onChange={(e) => setSearch(e.target.value)}
style={{ marginBottom: "10px", display: "block" }}
/>
{/* Form to add tasks */}
<form onSubmit={addTask}>
<input
ref={inputRef}
type="text"
placeholder="Enter new task"
value={newTask}
onChange={(e) => setNewTask(e.target.value)}
/>
<button type="submit">Add Task</button>
</form>
{/* Render filtered task list */}
<ul>
{filteredTasks.map((task) => (
<li key={task.id}>{task.title || task.text}</li>
))}
</ul>
</div>
);
}
export default App;
useMemo ensures filtering is efficient.
useCallback prevents unnecessary re-creations of addTask.
Common Pitfalls with useMemo and useCallback
- Overusing them
- Don’t wrap everything. Use them only when there’s a performance issue.
- Forgetting dependencies
- Always include variables/functions that your memoized code depends on.
- Confusing the two
useMemo→ caches valuesuseCallback→ caches functions
What We’ve Achieved
- Learned how to use
useMemoanduseCallback - Optimized task filtering with memoization
- Improved our Task Tracker with search functionality
Learning Path
Post 1: Introduction to React Hooks (Why They Matter + Starter Project) – Read Post-1
Learn what hooks are, why they replaced class components, and set up the starter Task Tracker App.
Post 2: Mastering useState in React – Read Post 2
Manage state in functional components. Add new tasks dynamically with useState.
Post 3: Understanding useEffect (Side Effects + API Calls) – Read Post 3
Fetch tasks from an API, mimic lifecycle methods, and clean up side effects.
Post 4: Using useRef in React (Not Just for DOM Access) – Read Post 4
Control focus on the input field and learn how to store values without re-renders.
Post 5: Creating Custom Hooks (useLocalStorage) – Read Post 5
Extract reusable logic into a custom hook that persists tasks across refreshes.
Post 6: Optimizing Performance with useMemo and useCallback – Current Post
Add a search feature and optimize it with memoization to prevent unnecessary re-renders.
Post 7 (Final): Building a Complete Task Tracker with React Hooks – Read Post 7
Add delete + toggle complete functionality, and wrap up with a fully functional app.
Discover more from TACETRA
Subscribe to get the latest posts sent to your email.