Try @eslint-react/kit@beta
logoESLint React

use-memo

Validates that 'useMemo' is called with a callback that returns a value.

Full Name in eslint-plugin-react-x

react-x/use-memo

Full Name in @eslint-react/eslint-plugin

@eslint-react/use-memo

Presets

x recommended recommended-typescript recommended-type-checked strict strict-typescript strict-type-checked

Rule Details

This rule ensures that useMemo() callbacks follow React's requirements. It checks for several common mistakes:

  1. Callbacks should not accept parameters (useMemo callbacks are called with no arguments)
  2. Callbacks should not be async or generator functions (must return a value synchronously)
  3. Callbacks should not reassign variables declared outside the callback (must be pure)
  4. Callbacks should return a value (useMemo is for computing values, not side effects)
  5. The result of useMemo should be used (not discarded)

Examples

useMemo callback receiving parameters

useMemo callbacks do not accept any parameters. All required data should be captured via closure.

// Problem: callback receives parameter c
import { useMemo } from "react";

function Component({ a, b }) {
  const x = useMemo((c) => a + c, [a]);
  return <div>{x}</div>;
}
// Recommended: use external variables directly via closure
import { useMemo } from "react";

function Component({ a, b }) {
  const x = useMemo(() => a + b, [a, b]);
  return <div>{x}</div>;
}

Using async or generator functions in useMemo

useMemo is for synchronous value computation and does not support async or generator functions.

// Problem: using an async callback
import { useMemo } from "react";

function Component({ a }) {
  const x = useMemo(async () => {
    //             ^^^^^^^^^^^^^ Async and generator functions are not supported
    return await fetchData(a);
  }, [a]);
  return <div>{x}</div>;
}

Reassigning external variables in a useMemo callback

useMemo callbacks must be pure functions and should not modify variables in the outer scope.

// Problem: callback reassigns external variable x
import { useMemo } from "react";

function Component() {
  let x;
  const y = useMemo(() => {
    x = [];
    // ^^^ Cannot reassign variable
    return true;
  }, []);
  return <div>{x}{y}</div>;
}
// Recommended: only read external variables, do not modify them
import { useMemo } from "react";

function Component({ a, b }) {
  const sum = useMemo(() => a + b, [a, b]);
  return <div>{sum}</div>;
}

useMemo callback without a return value

The purpose of useMemo is to cache computed results, so the callback must explicitly return a value.

// Problem: callback has no return, result is always undefined
import { useMemo } from "react";

function Component({ data }) {
  const processed = useMemo(() => {
    data.forEach((item) => console.log(item));
  }, [data]);
  return <div>{processed}</div>;
}
// Recommended: return the computed value
import { useMemo } from "react";

function Component({ data }) {
  const processed = useMemo(() => {
    return data.map((item) => item * 2);
  }, [data]);
  return <div>{processed}</div>;
}

Calling useMemo but not using its return value

If the result of useMemo is not assigned or used, caching is pointless.

// Problem: useMemo return value is discarded
import { useMemo } from "react";

function Component({ user }) {
  useMemo(() => {
    // ^^^^^^ useMemo() result is unused
    return heavyComputation(user);
  }, [user]);
  return <div />;
}
// Recommended: assign the result to a variable and use it in JSX
import { useMemo } from "react";

function Component({ items }) {
  const sorted = useMemo(() => [...items].sort(), [items]);
  return <div>{sorted.join(", ")}</div>;
}

Correctly returning computed values

When the callback correctly returns a synchronous computed value, useMemo works as expected.

// OK: returning a mapped array
import { useMemo } from "react";

function Component({ data }) {
  const processed = useMemo(() => {
    return data.map((item) => item * 2);
  }, [data]);
  return <div>{processed}</div>;
}

Using concise arrow functions

Using arrow functions with implicit return makes useMemo callbacks more concise.

// OK: concise arrow function directly returns the computed result
import { useMemo } from "react";

function Component({ items }) {
  const sorted = useMemo(() => [...items].sort(), [items]);
  return <div>{sorted.join(", ")}</div>;
}

Troubleshooting

I need to run side effects when dependencies change

You might try to use useMemo for side effects:

// Problem: Side effects in useMemo
function Component({ user }) {
  // No return value, just side effect
  useMemo(() => {
    analytics.track("UserViewed", { userId: user.id });
  }, [user.id]);

  // Not assigned to a variable
  useMemo(() => {
    return analytics.track("UserViewed", { userId: user.id });
  }, [user.id]);
}

If the side effect needs to happen in response to user interaction, it's best to colocate the side effect with the event:

// Recommended: Side effects in event handlers
function Component({ user }) {
  const handleClick = () => {
    analytics.track("ButtonClicked", { userId: user.id });
    // Other click logic...
  };

  return <button onClick={handleClick}>Click me</button>;
}

If the side effect synchronizes React state with some external state (or vice versa), use useEffect:

// Recommended: Synchronization in useEffect
function Component({ theme }) {
  useEffect(() => {
    localStorage.setItem("preferredTheme", theme);
    document.body.className = theme;
  }, [theme]);

  return <div>Current theme: {theme}</div>;
}

Versions

Resources

Further Reading


See Also

  • react-x/use-state
    Enforces correct usage of useState, including destructuring, symmetric naming of the value and setter, and wrapping expensive initializers in a lazy initializer function.

On this page