0Pricing
React Native Academy · Lesson

Memoization with React.memo, useCallback, useMemo

Wrap expensive child components with React.memo, stabilize callback props with useCallback, and cache derived values with useMemo to prevent redundant renders.

Memoization with React.memo, useCallback, useMemo is a free React Native Academy lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the React Native Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Why Components Re-Render

In React, a component re-renders whenever its parent re-renders, its state changes, or its context changes. When a parent re-renders, all child components re-render by default — even if their props have not changed. For a list with 50 items, this means 50 re-renders on every parent state change.

Memoization is the technique of caching the result of a computation and returning the cached result when the inputs have not changed. React provides three memoization tools: React.memo, useCallback, and useMemo.

React.memo for Component Memoization

React.memo is a higher-order component that wraps a functional component and prevents re-renders when the component's props have not changed (using shallow equality comparison). If the parent re-renders but passes the same props, the memoized child is skipped.

Wrap a component with React.memo when it receives stable props and is expensive to render. Row components in a FlatList are the classic use case — they receive the same item data on every parent scroll event.

import React from 'react';
import { View, Text } from 'react-native';

interface PostRowProps {
  title: string;
  author: string;
}

// Without memo: re-renders on every parent update
// function PostRow({ title, author }: PostRowProps) {

// With memo: skips re-render if title and author are unchanged
const PostRow = React.memo(function PostRow({ title, author }: PostRowProps) {
  return (
    <View>
      <Text>{title}</Text>
      <Text>{author}</Text>
    </View>
  );
});

export default PostRow;

React.memo Custom Comparison

By default, React.memo uses a shallow comparison — it checks if each top-level prop is strictly equal (===). For objects and arrays, shallow equality means the reference must be the same, not just the content.

If your component receives an object prop and you want to compare by value rather than reference, pass a custom comparison function as the second argument to React.memo. The function receives the previous and next props and returns true if the component should not re-render.

const PostRow = React.memo(
  function PostRow({ post }) {
    return <Text>{post.title}</Text>;
  },
  // Custom comparison: skip re-render if post.id and post.title are the same
  (prevProps, nextProps) => {
    return (
      prevProps.post.id === nextProps.post.id &&
      prevProps.post.title === nextProps.post.title
    );
  }
);

useCallback: Stabilizing Function References

When a parent component defines a function inside its render body, that function is recreated on every render, producing a new reference each time. If that function is passed as a prop to a React.memo child, the memo is defeated because the prop reference always changes.

useCallback(fn, deps) memoizes the function reference and only creates a new function when the values in the dependency array change. This keeps the prop reference stable across renders, allowing React.memo to work correctly.

import { useCallback } from 'react';

export function PostList() {
  const [posts, setPosts] = useState([]);

  // Without useCallback: new function reference on every render
  // const handleDelete = (id) => deletePost(id);

  // With useCallback: stable reference, memoized child is not re-rendered
  const handleDelete = useCallback((id: string) => {
    deletePost(id);
    setPosts((prev) => prev.filter((p) => p.id !== id));
  }, []); // No deps: function never changes

  return (
    <FlatList
      data={posts}
      renderItem={({ item }) => (
        <PostRow post={item} onDelete={handleDelete} />
      )}
    />
  );
}

useMemo: Caching Expensive Computations

useMemo(fn, deps) memoizes the return value of a function. React runs the function once, caches the result, and returns the cached value on subsequent renders until the dependency array values change.

Use useMemo for expensive operations like sorting or filtering large arrays, transforming API data, or computing derived values from state. Do not use it for cheap computations — the memoization overhead can exceed the benefit.

import { useMemo } from 'react';

export function ContactList({ contacts, searchQuery }) {
  // Recomputed only when contacts or searchQuery changes
  const filteredContacts = useMemo(() => {
    if (!searchQuery) return contacts;
    const query = searchQuery.toLowerCase();
    return contacts.filter((c) =>
      c.name.toLowerCase().includes(query) ||
      c.email.toLowerCase().includes(query)
    );
  }, [contacts, searchQuery]);

  return (
    <FlatList
      data={filteredContacts}
      keyExtractor={(item) => item.id}
      renderItem={({ item }) => <ContactRow contact={item} />}
    />
  );
}

Dependency Arrays: The Key to Correctness

Both useCallback and useMemo rely on dependency arrays to know when to recompute. Every variable from the outer scope that is used inside the memoized function must be listed in the dependency array — otherwise the cached value will be stale.

The ESLint plugin eslint-plugin-react-hooks enforces this rule automatically. The exhaustive-deps rule warns if you omit a dependency. Missing deps are the most common source of subtle bugs with memoization.

// WRONG: userId is used inside but not in deps
const fetchPosts = useCallback(async () => {
  const posts = await api.getPostsByUser(userId); // stale userId!
  setPosts(posts);
}, []); // Missing dependency: userId

// CORRECT: all used external values listed
const fetchPosts = useCallback(async () => {
  const posts = await api.getPostsByUser(userId);
  setPosts(posts);
}, [userId]); // Recomputed when userId changes

Memoizing Context Values

When you provide a Context value, React compares the value reference on every render. If the context value is an object literal created inline, it gets a new reference every render, causing all context consumers to re-render even if no data actually changed.

Wrap the context value in useMemo to stabilize its reference. Split context into separate providers when possible — for example, keep user data and UI theme in separate contexts so a theme change does not re-render all components that consume user data.

const ThemeContext = createContext(null);

export function ThemeProvider({ children }) {
  const [isDark, setIsDark] = useState(false);

  // Without useMemo: new object reference on every render
  // value={{ isDark, toggleTheme: () => setIsDark(d => !d) }}

  // With useMemo: stable reference, consumers only re-render when isDark changes
  const value = useMemo(() => ({
    isDark,
    toggleTheme: () => setIsDark((d) => !d),
  }), [isDark]);

  return (
    <ThemeContext.Provider value={value}>
      {children}
    </ThemeContext.Provider>
  );
}

When NOT to Memoize

Memoization is not free. useMemo and useCallback run the comparison on every render. For simple components and cheap operations, this overhead can make performance worse, not better.

Avoid memoization when: the component renders infrequently, the computation is trivially fast (like adding two numbers), or the dependencies change on every render (making the memo useless). Apply memoization only where the React Profiler shows a measurable improvement.

// ❌ Over-memoization: computation is trivial, no benefit
const fullName = useMemo(() => {
  return firstName + ' ' + lastName;
}, [firstName, lastName]);

// ✅ Better: just compute it
const fullName = firstName + ' ' + lastName;

// ✅ Good use of useMemo: sorting 5000 items is expensive
const sortedItems = useMemo(() => {
  return [...items].sort(compareByDate);
}, [items]);

The useCallback Pattern for FlatList renderItem

FlatList re-renders item rows when its renderItem prop changes. Defining renderItem as an inline arrow function creates a new reference on every parent render, defeating any memoization on the item component.

Define renderItem with useCallback and add only the dependencies that truly change its behavior. Also memoize the keyExtractor function for the same reason — stable references allow FlatList to optimize its reconciliation.

const renderItem = useCallback(
  ({ item }) => <PostRow post={item} onDelete={handleDelete} />,
  [handleDelete] // handleDelete is already wrapped in useCallback
);

const keyExtractor = useCallback(
  (item) => item.id,
  [] // Pure function, no deps
);

return (
  <FlatList
    data={posts}
    renderItem={renderItem}
    keyExtractor={keyExtractor}
  />
);

Using the Profiler to Verify Memoization Works

After applying memoization, use the React Profiler to verify it is actually working. Record the same interaction and compare the flame graph before and after. Memoized components should appear grayed out in the profiler (indicating they were skipped) rather than colored bars (indicating they rendered).

If a memoized component still shows up as rendered, check the Why did this render? panel in DevTools. It will tell you exactly which prop or hook changed, revealing an unstable reference or a missing memoization step.

Summary: When to Use Each Tool

A quick decision guide for React memoization tools:

  • React.memo — wrap a component whose parent re-renders often but whose own props change rarely
  • useCallback — stabilize a function reference passed as a prop to a memoized child, or used as a dependency of another hook
  • useMemo — cache an expensive derived value (filtering, sorting, transforming large arrays) that is recomputed on every render

Always profile first to confirm there is a real problem, apply one optimization at a time, and measure again to confirm the improvement.

Quick Check

Test your understanding of React Native Mobile Development concepts from this lesson.

Lesson Recap

In this lesson you learned: how React.memo prevents child re-renders when props have not changed, how useCallback stabilizes function references to make React.memo effective, and how useMemo caches expensive computed values across renders. Next up we tune FlatList rendering performance for smooth list scrolling.

Frequently asked questions

Is the “Memoization with React.memo, useCallback, useMemo” lesson free?

Yes — the full text of “Memoization with React.memo, useCallback, useMemo” is free to read here on the web, and the React Native Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the React Native Academy course, upgrade to CoddyKit PRO.

What will I learn in “Memoization with React.memo, useCallback, useMemo”?

Wrap expensive child components with React.memo, stabilize callback props with useCallback, and cache derived values with useMemo to prevent redundant renders. You practise React Native Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start React Native Academy?

No prior experience is required. React Native Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Memoization with React.memo, useCallback, useMemo” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this React Native Academy lesson?

Yes. Every React Native Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Profiling with Flipper and React DevTools
  2. Memoization with React.memo, useCallback, useMemo
  3. FlatList Performance Tuning
  4. Bundle Size and Lazy Loading
← Back to React Native Academy