@@ -232,45 +232,6 @@ const tests = {
232232 }
233233 }
234234 ` ,
235- `
236- // Currently valid because we found this to be a common pattern
237- // for feature flag checks in existing components.
238- // We *could* make it invalid but that produces quite a few false positives.
239- // Why does it make sense to ignore it? Firstly, because using
240- // hooks in a class would cause a runtime error anyway.
241- // But why don't we care about the same kind of false positive in a functional
242- // component? Because even if it was a false positive, it would be confusing
243- // anyway. So it might make sense to rename a feature flag check in that case.
244- class ClassComponentWithFeatureFlag extends React.Component {
245- render() {
246- if (foo) {
247- useFeatureFlag();
248- }
249- }
250- }
251- ` ,
252- `
253- // Currently valid because we don't check for hooks in classes.
254- // See ClassComponentWithFeatureFlag for rationale.
255- // We *could* make it invalid if we don't regress that false positive.
256- class ClassComponentWithHook extends React.Component {
257- render() {
258- React.useState();
259- }
260- }
261- ` ,
262- `
263- // Currently valid.
264- // These are variations capturing the current heuristic--
265- // we only allow hooks in PascalCase, useFoo functions,
266- // or classes (due to common false positives and because they error anyway).
267- // We *could* make some of these invalid.
268- // They probably don't matter much.
269- (class {useHook = () => { useState(); }});
270- (class {useHook() { useState(); }});
271- (class {h = () => { useState(); }});
272- (class {i() { useState(); }});
273- ` ,
274235 `
275236 // Valid because they're not matching use[A-Z].
276237 fooState();
@@ -870,6 +831,52 @@ const tests = {
870831 ` ,
871832 errors : [ topLevelError ( 'useBasename' ) ] ,
872833 } ,
834+ {
835+ code : `
836+ class ClassComponentWithFeatureFlag extends React.Component {
837+ render() {
838+ if (foo) {
839+ useFeatureFlag();
840+ }
841+ }
842+ }
843+ ` ,
844+ errors : [ classError ( 'useFeatureFlag' ) ] ,
845+ } ,
846+ {
847+ code : `
848+ class ClassComponentWithHook extends React.Component {
849+ render() {
850+ React.useState();
851+ }
852+ }
853+ ` ,
854+ errors : [ classError ( 'React.useState' ) ] ,
855+ } ,
856+ {
857+ code : `
858+ (class {useHook = () => { useState(); }});
859+ ` ,
860+ errors : [ classError ( 'useState' ) ] ,
861+ } ,
862+ {
863+ code : `
864+ (class {useHook() { useState(); }});
865+ ` ,
866+ errors : [ classError ( 'useState' ) ] ,
867+ } ,
868+ {
869+ code : `
870+ (class {h = () => { useState(); }});
871+ ` ,
872+ errors : [ classError ( 'useState' ) ] ,
873+ } ,
874+ {
875+ code : `
876+ (class {i() { useState(); }});
877+ ` ,
878+ errors : [ classError ( 'useState' ) ] ,
879+ } ,
873880 ] ,
874881} ;
875882
@@ -919,6 +926,15 @@ function topLevelError(hook) {
919926 } ;
920927}
921928
929+ function classError ( hook ) {
930+ return {
931+ message :
932+ `React Hook "${ hook } " cannot be called in a class component. React Hooks ` +
933+ 'must be called in a React function component or a custom React ' +
934+ 'Hook function.' ,
935+ } ;
936+ }
937+
922938// For easier local testing
923939if ( ! process . env . CI ) {
924940 let only = [ ] ;
0 commit comments