diff --git a/packages/eslint-plugin-react-hooks/CHANGELOG.md b/packages/eslint-plugin-react-hooks/CHANGELOG.md index 8a0882d24ec40..046c81547642f 100644 --- a/packages/eslint-plugin-react-hooks/CHANGELOG.md +++ b/packages/eslint-plugin-react-hooks/CHANGELOG.md @@ -1,3 +1,10 @@ +## Next Release + +* **New Violations:** Components now need to be named with a capital letter, potentially prefixed with an underscore. E.g. `Button` or `_Button`. `_component` is no longer valid. ([@kassens](https://github.com/kassens)) in [#25162](https://github.com/facebook/react/pull/25162) +* Hooks can now have an optional underscore prefix, e.g. `_useName`. ([@kassens](https://github.com/kassens)) in [#25162](https://github.com/facebook/react/pull/25162) + +## 4.6.0 + ## 4.5.0 * Fix false positive error with large number of branches. ([@scyron6](https://github.com/scyron6) in [#24287](https://github.com/facebook/react/pull/24287)) diff --git a/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js b/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js index e634633cdf384..d194ba93b366e 100644 --- a/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js +++ b/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js @@ -200,11 +200,10 @@ const tests = { }) `, ` - // Valid because they're not matching use[A-Z]. + // Valid because they're not matching ^_*use[A-Z]. fooState(); use(); _use(); - _useState(); use_hook(); // also valid because it's not matching the PascalCase namespace jest.useFakeTimer() @@ -406,17 +405,6 @@ const tests = { const [myState, setMyState] = useState(null); } `, - ` - // Valid, but should be invalid. '_useHook' is currently recognized as a component. - function Component(props) { - if (cond) { - _useHook(); - } - } - function _useHook() { - useState(null); - } - `, ], invalid: [ { @@ -443,6 +431,7 @@ const tests = { `, errors: [ topLevelError('Hook.useState'), + topLevelError('Hook._useState'), topLevelError('Hook.use42'), topLevelError('Hook.useHook'), ], @@ -652,6 +641,20 @@ const tests = { functionError('useHookInsideNormalFunction', 'normalFunctionWithHook'), ], }, + { + code: ` + // These are neither functions nor hooks. + function _normalFunctionWithHook() { + useHookInsideNormalFunction(); + } + function _useNotAHook() { + useHookInsideNormalFunction(); + } + `, + errors: [ + functionError('useHookInsideNormalFunction', '_normalFunctionWithHook'), + ], + }, { code: ` // Invalid because it's dangerous and might not warn otherwise. @@ -893,6 +896,7 @@ const tests = { // they are confusing anyway due to the use*() convention // already being associated with Hooks. useState(); + _useState(); if (foo) { const foo = React.useCallback(() => {}); } @@ -900,10 +904,25 @@ const tests = { `, errors: [ topLevelError('useState'), + topLevelError('_useState'), topLevelError('React.useCallback'), topLevelError('useCustomHook'), ], }, + { + code: ` + // Valid, but should be invalid. '_useHook' is currently recognized as a component. + function Component(props) { + if (cond) { + _useHook(); + } + } + function _useHook() { + useState(null); + } + `, + errors: [conditionalError('_useHook')], + }, { code: ` // Technically this is a false positive. diff --git a/packages/eslint-plugin-react-hooks/src/RulesOfHooks.js b/packages/eslint-plugin-react-hooks/src/RulesOfHooks.js index 31d54e813ae65..31b5a4bdc82c1 100644 --- a/packages/eslint-plugin-react-hooks/src/RulesOfHooks.js +++ b/packages/eslint-plugin-react-hooks/src/RulesOfHooks.js @@ -16,7 +16,7 @@ */ function isHookName(s) { - return /^use[A-Z0-9].*$/.test(s); + return /^_*use[A-Z0-9]/.test(s); } /** @@ -42,16 +42,13 @@ function isHook(node) { /** * Checks if the node is a React component name. React component names must - * always start with a non-lowercase letter. So `MyComponent` or `_MyComponent` - * are valid component names for instance. + * always start with an uppercase letter, potentially prefixed with `_`. + * So `MyComponent` or `_MyComponent` are valid component names for instance. + * `_myComponent` is not a valid component name. */ function isComponentName(node) { - if (node.type === 'Identifier') { - return !/^[a-z]/.test(node.name); - } else { - return false; - } + return node.type === 'Identifier' && /^_*[A-Z]/.test(node.name); } function isReactFunction(node, functionName) {