Skip to content
Open
2 changes: 2 additions & 0 deletions src/react-hooks-nesting-walker/error-messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,6 @@ export const ERROR_MESSAGES = {
'A hook cannot be used inside of another function',
invalidFunctionExpression: 'A hook cannot be used inside of another function',
hookAfterEarlyReturn: 'A hook should not appear after a return statement',
anonymousFunctionIllegalCallback:
'Hook is in an anonymous function that is passed to an illegal callback',
};
15 changes: 15 additions & 0 deletions src/react-hooks-nesting-walker/react-hooks-nesting-walker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,21 @@ export class ReactHooksNestingWalker extends RuleWalker {
return;
}

/**
* Detect if the unnamed expression is wrapped in a illegal function call
*/
if (
isCallExpression(ancestor.parent) &&
isIdentifier(ancestor.parent.expression) &&
!isReactComponentDecorator(ancestor.parent.expression)
) {
this.addFailureAtNode(
hookNode,
ERROR_MESSAGES.anonymousFunctionIllegalCallback,
);
return;
}

/**
* Allow using hooks when the function is passed to `React.memo` or `React.forwardRef`
*/
Expand Down
19 changes: 19 additions & 0 deletions test/tslint-rule/anonymous-function-error.ts.lint
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
const IllegalComponent = wrapper(function() {
useEffect(() => {});
~~~~~~~~~~~~~~~~~~~ [Hook is in an anonymous function that is passed to an illegal callback]
})

const LegalAnonymousComponent = function() {
useEffect(() => {});
}

const ForwardedComponent = React.forwardRef(function(props, ref) {
useEffect(() => {
console.log("I am legal")
});
})

const MemoizedComponent = React.memo((props) => {
const [state, setState] = React.useState(props.initValue);
return <span>{state}</span>
})