Skip to content

fix(linter): recognize stable hook results declared with let#8927

Merged
mdevils merged 4 commits intobiomejs:mainfrom
littleKitchen:fix/8907-let-useState-stable
Feb 2, 2026
Merged

fix(linter): recognize stable hook results declared with let#8927
mdevils merged 4 commits intobiomejs:mainfrom
littleKitchen:fix/8907-let-useState-stable

Conversation

@littleKitchen
Copy link
Contributor

Summary

When useState setters or useRef values are destructured into let bindings, they should still be recognized as stable and not require inclusion in dependency arrays.

Problem

let [a, setA] = useState(0);
let b = useRef("");

useEffect(() => {
  setA(1);
  b.current = "test";
}, []); // ❌ Biome incorrectly reports setA and b as missing dependencies

The useExhaustiveDependencies rule was returning early for non-const declarations without checking if the initializer was a stable hook call.

Solution

Check stable hook results for both let and const declarations. For let declarations, only stable hook results (like useState setters at index 1, or useRef identity) are considered stable.

Test Plan

  • Added issue8907.js test case
  • All 56 useExhaustiveDependencies tests pass

Fixes #8907

When `useState` setters or `useRef` values are destructured into `let`
bindings (e.g., `let [a, setA] = useState(0)`), they should still be
recognized as stable and not require inclusion in dependency arrays.

The previous code returned early for non-const declarations without
checking if the initializer was a stable hook call. This fix checks
stable hook results for both `let` and `const` declarations.

Fixes biomejs#8907
@changeset-bot
Copy link

changeset-bot bot commented Feb 1, 2026

🦋 Changeset detected

Latest commit: caa1010

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 Feb 1, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 1, 2026

Walkthrough

This PR fixes the useExhaustiveDependencies rule to correctly recognise stable hook results (like useState setters and useRef values) when declared with let instead of just const. The fix extends the is_stable_binding function to handle non-const declarations by checking for reassignments, validating destructuring patterns, and determining whether initializers are stable React hook calls. Test cases demonstrate the expected behaviour for both stable (non-reassigned) and unstable (reassigned) bindings.

Possibly related PRs

Suggested reviewers

  • dyc3
  • ematipico
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: fixing the linter to recognize stable hook results declared with let.
Description check ✅ Passed The description clearly explains the problem, solution, and test plan, all directly related to the changeset.
Linked Issues check ✅ Passed The PR fully addresses issue #8907 by implementing stable hook result recognition for both let and const declarations, with appropriate test coverage.
Out of Scope Changes check ✅ Passed All changes are focused on fixing the useExhaustiveDependencies rule and adding related tests; no unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@dyc3
Copy link
Contributor

dyc3 commented Feb 1, 2026

cc @mdevils

Copy link
Contributor

@mdevils mdevils left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@littleKitchen thank you for your contributions! Please check my comments.

};

// For non-const declarations, only stable hook results (like useState setters
// or useRef) are considered stable. Other values may be reassigned.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other values may be reassigned.

All lets may be reassigned. eslint rule checks specifically if it was reassigned in case of stable hook results. Please check all writes of the binding to make sure it wasn't rewritten.

@mdevils mdevils self-assigned this Feb 2, 2026
@littleKitchen
Copy link
Contributor Author

I see, thanks for the review.

Addresses reviewer feedback: verify that the let binding was not
reassigned before treating it as stable. If the binding has any writes,
return false (not stable).

Also adds test case for reassigned useState setter and useRef.
@mdevils
Copy link
Contributor

mdevils commented Feb 2, 2026

@littleKitchen thank you for your quick response and fixes!

Please include a changeset for your changes.

Here is an example changeset: https://github.com/biomejs/biome/blob/main/.changeset/ninety-tigers-fetch.md

You can create a new changeset using just new-changeset command.

@littleKitchen littleKitchen requested a review from mdevils February 2, 2026 19:28
@codspeed-hq
Copy link

codspeed-hq bot commented Feb 2, 2026

Merging this PR will not alter performance

✅ 58 untouched benchmarks
⏩ 95 skipped benchmarks1


Comparing littleKitchen:fix/8907-let-useState-stable (caa1010) with main (d876a38)

Open in CodSpeed

Footnotes

  1. 95 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.

@mdevils
Copy link
Contributor

mdevils commented Feb 2, 2026

@littleKitchen thank you for your contribution!

@mdevils mdevils merged commit 0ef3da5 into biomejs:main Feb 2, 2026
18 checks passed
@github-actions github-actions bot mentioned this pull request Feb 2, 2026
@littleKitchen
Copy link
Contributor Author

Thanks!

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.

💅 useExhaustiveDependencies mistakenly reports setters returned from useState() and refs declared with let as required effect dependencies

3 participants