Skip to content

fix(lint/useExhaustiveDependencies): correct fix for method calls#7709

Merged
siketyan merged 1 commit intomainfrom
fix/GH-7704
Oct 10, 2025
Merged

fix(lint/useExhaustiveDependencies): correct fix for method calls#7709
siketyan merged 1 commit intomainfrom
fix/GH-7704

Conversation

@siketyan
Copy link
Copy Markdown
Member

@siketyan siketyan commented Oct 7, 2025

Summary

The useExhaustiveDependencies rule now correctly adds the object as a dependency when its method is called within a React hook. It should be avoided to add the method itself as a dependency because it will break the prototype chain.

Test Plan

Added a snapshot test.

Docs

N/A

@siketyan siketyan requested review from a team October 7, 2025 16:03
@siketyan siketyan self-assigned this Oct 7, 2025
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Oct 7, 2025

🦋 Changeset detected

Latest commit: 5abba2b

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 13 packages
Name Type
@biomejs/biome Patch
@biomejs/cli-win32-x64 Patch
@biomejs/cli-win32-arm64 Patch
@biomejs/cli-darwin-x64 Patch
@biomejs/cli-darwin-arm64 Patch
@biomejs/cli-linux-x64 Patch
@biomejs/cli-linux-arm64 Patch
@biomejs/cli-linux-x64-musl Patch
@biomejs/cli-linux-arm64-musl Patch
@biomejs/wasm-web Patch
@biomejs/wasm-bundler Patch
@biomejs/wasm-nodejs Patch
@biomejs/backend-jsonrpc Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions github-actions Bot added A-Linter Area: linter L-JavaScript Language: JavaScript and super languages labels Oct 7, 2025
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Oct 7, 2025

Walkthrough

  • Adds a patch changeset describing a fix to the useExhaustiveDependencies rule: when a method is invoked inside a closure, the dependency becomes the object (e.g., [props]) instead of an empty array.
  • Updates get_whole_static_member_expression to return the object when the ancestor is followed by a call expression, introducing an early return in that case.
  • Adds tests (issue7704.js) covering empty deps, function-prop deps, and object-prop deps for useEffect with props.foo().

Possibly related PRs

Suggested labels

A-Linter, L-JavaScript

Suggested reviewers

  • arendjr

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title explicitly references the affected lint rule (useExhaustiveDependencies) and concisely describes the core change (correcting method call handling), directly matching the pull request’s main purpose.
Description Check ✅ Passed The pull request description clearly outlines the change to useExhaustiveDependencies, explains why methods should not be tracked directly, and references the added snapshot test, all of which align closely with the alterations in the changeset.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/GH-7704

Comment @coderabbitai help to get the list of available commands and usage tips.

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Oct 7, 2025

CodSpeed Performance Report

Merging #7709 will not alter performance

Comparing fix/GH-7704 (5abba2b) with main (3eeb4df)

Summary

✅ 53 untouched
⏩ 85 skipped1

Footnotes

  1. 85 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@siketyan siketyan merged commit d6da4d5 into main Oct 10, 2025
18 checks passed
@siketyan siketyan deleted the fix/GH-7704 branch October 10, 2025 04:00
@github-actions github-actions Bot mentioned this pull request Oct 9, 2025
@AfrazHussain
Copy link
Copy Markdown

AfrazHussain commented Oct 15, 2025

Hi @siketyan ,

Thanks for this PR. I agree with the rest of the changes, but can you please explain why granular dependencies are now invalid? I'm referring to the following example:

function InvalidComponent2(props) {
	useEffect(() => {
		props.foo();
	}, [props.foo]);
}

This is a very common pattern, and without this pattern, the effect would run when any prop changes, and not just props.foo.

@siketyan
Copy link
Copy Markdown
Member Author

siketyan commented Oct 16, 2025

@AfrazHussain Actually calling props.foo() references the props variable itself as the this parameter, not only props.foo. Thus we should declare it as a dependency of the useEffect hook. This is the same behaviour as eslint-plugin-react-hooks - see the related discussion: facebook/react#16265

@scarlac
Copy link
Copy Markdown

scarlac commented Oct 17, 2025

Agree with @AfrazHussain. The "fix" caused a regression in behavior.
The main object reference can change but that should not cause the effect to rerun.

// render 1
const [foo] = useState(() => ({ bar: 42 }));
useEffect(() => { console.log('foo.bar', foo.bar); }, [foo]); // runs, first render.

And after modifying foo.bar:

// render 2a - we now pretend foo.bar=21 was called (ie foo was not mutated but bar was), so "foo" remains the same reference
const [foo] = useState(() => ({ bar: 42 }));
useEffect(() => { console.log('foo.bar', foo.bar); }, [foo]); // Error: Does not re-run as required

But it should have been:

// render 2b - we now pretend foo.bar has been modified directly, but "foo" remains the same object
const [foo] = useState(() => ({ bar: 42 }));
useEffect(() => { console.log('foo.bar', foo.bar); }, [foo.bar]); // Re-runs as intended!

In render 2a, it's assumed that the code author is using immutability.
In render 2b, no immutability is assumed, and code re-runs as intended.

This is backed up by ESLint's behavior as seen here on this playground link:
ESLint Online Plyground

Screenshot 2025-10-17 at 11 52 01 AM

You'll notice that it will be silenced if you add either foo or foo.bar, however foo.bar is the safer option here and foo is only safe if the developer knows it is. Thus, the rule will suggest foo.bar, not foo.

Screenshot 2025-10-17 at 11 53 33 AM

@scarlac
Copy link
Copy Markdown

scarlac commented Oct 17, 2025

Thank you. Perhaps my report is misplaced then. I will open a new ticket, since I'm seeing regressions related to what was changed here, although it may not be exactly the same (This hook specifies a dependency more specific that its captures: foo.bar[lint](https://biomejs.dev/linter/rules/use-exhaustive-dependencies))

Opened a new issue here: #7782

@AfrazHussain
Copy link
Copy Markdown

@scarlac , Thanks for creating your issue. That is in fact what my comment was referring to as well like you correctly pointed out initially.

@siketyan The issue (facebook/react#16265) that you've linked is an open issue for the past 6 years, and even there people have mentioned work-arounds for this behaviour, so I don't think that if its the behaviour of eslint-plugin-react-hooks that it is correct. My issue is the same as the one that @scarlac mentions in #7782. The solution for me now is to downgrade to 2.2.5.

@LeCarbonator
Copy link
Copy Markdown

I’ve mentioned this previously in the Biomejs Discord, but it’s worth documenting here for future reference.

This is a concrete example of why methods can be unsafe as hook dependencies. I've encountered it a couple days ago at my workplace.

function Test() {
  const [values, setValues] = useState<string[]>([]);

  const computed = useMemo(
    () => values.map((v) => v.toUpperCase()),
    [
      // Expected: `values`, since it is used to derive a new array.
      // Proposed by Biome:
      values.map
      // This would always evaluate as stable because
      // `Array.prototype.map === Array.prototype.map`,
      // even when `values` changes.
    ],
  );
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Linter Area: linter L-JavaScript Language: JavaScript and super languages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants