Skip to content

Conversation

@afurm
Copy link

@afurm afurm commented Dec 28, 2025

Problem Description

A critical bug was identified in the eslint-plugin-react-hooks package where the "Rules of Hooks" were not being enforced for React Hooks called within anonymous default export arrow functions.

Example of Buggy Behavior

The following code should have triggered an ESLint error but was incorrectly marked as valid:

export default () => {
  if (isVal) {
    useState(0); // This conditional hook call was NOT flagged
  }
}

Root Cause Analysis

The issue resided in the getFunctionName() utility function within RulesOfHooks.ts. This function is responsible for determining the name of the function currently being analyzed.

  1. Missing Node Support: The function did not account for ExportDefaultDeclaration as a parent node for ArrowFunctionExpression or FunctionExpression.
  2. Validation Bypass: When getFunctionName() returned undefined for these anonymous default exports, the rule's logic fell into a lenient else block that skipped reporting unless the code was explicitly determined to be inside another known component or hook.
  3. Incomplete Scope Analysis: Because the function name was missing, the linter failed to recognize that the hook was being called in a context that is neither a valid React Function Component (which must be PascalCase) nor a Custom Hook (which must start with use).

Solution

The fix involved two primary changes to packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts:

1. Enhanced Function Name Detection

Updated getFunctionName() to detect when a function is a default export and return a synthetic identifier with the name "default".

// packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts

// ... inside getFunctionName ...
} else if (node.parent?.type === 'ExportDefaultDeclaration') {
  // Handle: export default () => {};
  return {type: 'Identifier', name: 'default'} as Node;
}

2. Robust Error Reporting

Improved the error reporting logic to handle synthetic identifiers. Previously, the code called getSourceCode().getText(codePathFunctionName), which would throw an error on synthetic nodes that don't exist in the actual source text.

// packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts

const functionNameText =
  'name' in codePathFunctionName && typeof codePathFunctionName.name === 'string'
    ? codePathFunctionName.name
    : getSourceCode().getText(codePathFunctionName);

// Resulting error message:
// React Hook "useState" is called in function "default" that is neither a React function component...

Verification Results

Automated Tests

The fix was verified by moving existing "TODO" test cases in ESLintRulesOfHooks-test.js from the valid suite to the invalid suite.

  • Test Suite: packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js
  • Total Tests Passed: 1420
  • Regressions: None. The fix specifically targeted the default export case without affecting the intentional leniency for generic callbacks outside of React contexts.

Impact

This fix ensures that developers using anonymous default exports (a common pattern in React) are properly warned when they violate the Rules of Hooks, preventing potential runtime bugs related to hook execution order.

@meta-cla meta-cla bot added the CLA Signed label Dec 28, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant