use-memo
Validates that 'useMemo' is called with a callback that returns a value.
Full Name in eslint-plugin-react-x
react-x/use-memoFull Name in @eslint-react/eslint-plugin
@eslint-react/use-memoPresets
x
recommended
recommended-typescript
recommended-type-checked
strict
strict-typescript
strict-type-checked
Rule Details
The React Compiler automatically memoizes values and functions, reducing the need to write useMemo by hand. However, existing useMemo callbacks must still follow React's requirements (e.g. purity), and this rule validates their correctness.
This rule ensures that useMemo() callbacks follow React's requirements. It checks for several common mistakes:
- Callbacks should not accept parameters (useMemo callbacks are called with no arguments)
- Callbacks should not be async or generator functions (must return a value synchronously)
- Callbacks should not reassign variables declared outside the callback (must be pure)
- Callbacks should return a value (useMemo is for computing values, not side effects)
- 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>;
}Resources
Further Reading
See Also
react-x/use-state
Enforces correct usage ofuseState, including destructuring, symmetric naming of the value and setter, and wrapping expensive initializers in a lazy initializer function.