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
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>;
}Versions
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.