@samitouri / QOS-React / commits / 57b16e3788

[lint] Remove experimental gating useEffectEvent rules (#34660)

Stacked on https://github.com/facebook/react/pull/34637 `useEffectEvent` is now in canary so we need to remove this `__EXPERIMENTAL__` gating on the rules and tests

Jack Pope committed Sep 30, 2025 at 16:55 UTC 57b16e37887b324e4a077fd302b805d63421b695
4 files changed +161 -185
packages/eslint-plugin-react-hooks/__tests__/ESLintRuleExhaustiveDeps-test.js
+17 -27
@@ -1549,6 +1549,21 @@ const tests = {
1549 },
1550 },
1551 },
1552 + {
1553 + code: normalizeIndent`
1554 + function MyComponent({ theme }) {
1555 + const onStuff = useEffectEvent(() => {
1556 + showNotification(theme);
1557 + });
1558 + useEffect(() => {
1559 + onStuff();
1560 + }, []);
1561 + React.useEffect(() => {
1562 + onStuff();
1563 + }, []);
1564 + }
1565 + `,
1566 + },
1567 ],
1568 invalid: [
1569 {
@@ -7819,31 +7834,6 @@ const tests = {
7834 },
7835 ],
7836 },
7822 - ],
7823 -};
7824 -
7825 -if (__EXPERIMENTAL__) {
7826 - tests.valid = [
7827 - ...tests.valid,
7828 - {
7829 - code: normalizeIndent`
7830 - function MyComponent({ theme }) {
7831 - const onStuff = useEffectEvent(() => {
7832 - showNotification(theme);
7833 - });
7834 - useEffect(() => {
7835 - onStuff();
7836 - }, []);
7837 - React.useEffect(() => {
7838 - onStuff();
7839 - }, []);
7840 - }
7841 - `,
7842 - },
7843 - ];
7844 -
7845 - tests.invalid = [
7846 - ...tests.invalid,
7837 {
7838 code: normalizeIndent`
7839 function MyComponent({ theme }) {
@@ -7907,8 +7897,8 @@ if (__EXPERIMENTAL__) {
7897 },
7898 ],
7899 },
7910 - ];
7911 -}
7900 + ],
7901 +};
7902
7903 // Tests that are only valid/invalid across parsers supporting Flow
7904 const testsFlow = {
packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js
+139 -148
@@ -602,6 +602,143 @@ const allTests = {
602 },
603 },
604 },
605 + {
606 + code: normalizeIndent`
607 + // Valid because functions created with useEffectEvent can be called in a useEffect.
608 + function MyComponent({ theme }) {
609 + const onClick = useEffectEvent(() => {
610 + showNotification(theme);
611 + });
612 + useEffect(() => {
613 + onClick();
614 + });
615 + React.useEffect(() => {
616 + onClick();
617 + });
618 + }
619 + `,
620 + },
621 + {
622 + code: normalizeIndent`
623 + // Valid because functions created with useEffectEvent can be passed by reference in useEffect
624 + // and useEffectEvent.
625 + function MyComponent({ theme }) {
626 + const onClick = useEffectEvent(() => {
627 + showNotification(theme);
628 + });
629 + const onClick2 = useEffectEvent(() => {
630 + debounce(onClick);
631 + debounce(() => onClick());
632 + debounce(() => { onClick() });
633 + deboucne(() => debounce(onClick));
634 + });
635 + useEffect(() => {
636 + let id = setInterval(() => onClick(), 100);
637 + return () => clearInterval(onClick);
638 + }, []);
639 + React.useEffect(() => {
640 + let id = setInterval(() => onClick(), 100);
641 + return () => clearInterval(onClick);
642 + }, []);
643 + return null;
644 + }
645 + `,
646 + },
647 + {
648 + code: normalizeIndent`
649 + function MyComponent({ theme }) {
650 + useEffect(() => {
651 + onClick();
652 + });
653 + const onClick = useEffectEvent(() => {
654 + showNotification(theme);
655 + });
656 + }
657 + `,
658 + },
659 + {
660 + code: normalizeIndent`
661 + function MyComponent({ theme }) {
662 + // Can receive arguments
663 + const onEvent = useEffectEvent((text) => {
664 + console.log(text);
665 + });
666 +
667 + useEffect(() => {
668 + onEvent('Hello world');
669 + });
670 + React.useEffect(() => {
671 + onEvent('Hello world');
672 + });
673 + }
674 + `,
675 + },
676 + {
677 + code: normalizeIndent`
678 + // Valid because functions created with useEffectEvent can be called in useLayoutEffect.
679 + function MyComponent({ theme }) {
680 + const onClick = useEffectEvent(() => {
681 + showNotification(theme);
682 + });
683 + useLayoutEffect(() => {
684 + onClick();
685 + });
686 + React.useLayoutEffect(() => {
687 + onClick();
688 + });
689 + }
690 + `,
691 + },
692 + {
693 + code: normalizeIndent`
694 + // Valid because functions created with useEffectEvent can be called in useInsertionEffect.
695 + function MyComponent({ theme }) {
696 + const onClick = useEffectEvent(() => {
697 + showNotification(theme);
698 + });
699 + useInsertionEffect(() => {
700 + onClick();
701 + });
702 + React.useInsertionEffect(() => {
703 + onClick();
704 + });
705 + }
706 + `,
707 + },
708 + {
709 + code: normalizeIndent`
710 + // Valid because functions created with useEffectEvent can be passed by reference in useLayoutEffect
711 + // and useInsertionEffect.
712 + function MyComponent({ theme }) {
713 + const onClick = useEffectEvent(() => {
714 + showNotification(theme);
715 + });
716 + const onClick2 = useEffectEvent(() => {
717 + debounce(onClick);
718 + debounce(() => onClick());
719 + debounce(() => { onClick() });
720 + deboucne(() => debounce(onClick));
721 + });
722 + useLayoutEffect(() => {
723 + let id = setInterval(() => onClick(), 100);
724 + return () => clearInterval(onClick);
725 + }, []);
726 + React.useLayoutEffect(() => {
727 + let id = setInterval(() => onClick(), 100);
728 + return () => clearInterval(onClick);
729 + }, []);
730 + useInsertionEffect(() => {
731 + let id = setInterval(() => onClick(), 100);
732 + return () => clearInterval(onClick);
733 + }, []);
734 + React.useInsertionEffect(() => {
735 + let id = setInterval(() => onClick(), 100);
736 + return () => clearInterval(onClick);
737 + }, []);
738 + return null;
739 + }
740 + `,
741 + },
742 ],
743 invalid: [
744 {
@@ -1407,152 +1544,6 @@ const allTests = {
1544 },
1545 errors: [useEffectEventError('onClick', true)],
1546 },
1410 - ],
1411 -};
1412 -
1413 -if (__EXPERIMENTAL__) {
1414 - allTests.valid = [
1415 - ...allTests.valid,
1416 - {
1417 - code: normalizeIndent`
1418 - // Valid because functions created with useEffectEvent can be called in a useEffect.
1419 - function MyComponent({ theme }) {
1420 - const onClick = useEffectEvent(() => {
1421 - showNotification(theme);
1422 - });
1423 - useEffect(() => {
1424 - onClick();
1425 - });
1426 - React.useEffect(() => {
1427 - onClick();
1428 - });
1429 - }
1430 - `,
1431 - },
1432 - {
1433 - code: normalizeIndent`
1434 - // Valid because functions created with useEffectEvent can be passed by reference in useEffect
1435 - // and useEffectEvent.
1436 - function MyComponent({ theme }) {
1437 - const onClick = useEffectEvent(() => {
1438 - showNotification(theme);
1439 - });
1440 - const onClick2 = useEffectEvent(() => {
1441 - debounce(onClick);
1442 - debounce(() => onClick());
1443 - debounce(() => { onClick() });
1444 - deboucne(() => debounce(onClick));
1445 - });
1446 - useEffect(() => {
1447 - let id = setInterval(() => onClick(), 100);
1448 - return () => clearInterval(onClick);
1449 - }, []);
1450 - React.useEffect(() => {
1451 - let id = setInterval(() => onClick(), 100);
1452 - return () => clearInterval(onClick);
1453 - }, []);
1454 - return null;
1455 - }
1456 - `,
1457 - },
1458 - {
1459 - code: normalizeIndent`
1460 - function MyComponent({ theme }) {
1461 - useEffect(() => {
1462 - onClick();
1463 - });
1464 - const onClick = useEffectEvent(() => {
1465 - showNotification(theme);
1466 - });
1467 - }
1468 - `,
1469 - },
1470 - {
1471 - code: normalizeIndent`
1472 - function MyComponent({ theme }) {
1473 - // Can receive arguments
1474 - const onEvent = useEffectEvent((text) => {
1475 - console.log(text);
1476 - });
1477 -
1478 - useEffect(() => {
1479 - onEvent('Hello world');
1480 - });
1481 - React.useEffect(() => {
1482 - onEvent('Hello world');
1483 - });
1484 - }
1485 - `,
1486 - },
1487 - {
1488 - code: normalizeIndent`
1489 - // Valid because functions created with useEffectEvent can be called in useLayoutEffect.
1490 - function MyComponent({ theme }) {
1491 - const onClick = useEffectEvent(() => {
1492 - showNotification(theme);
1493 - });
1494 - useLayoutEffect(() => {
1495 - onClick();
1496 - });
1497 - React.useLayoutEffect(() => {
1498 - onClick();
1499 - });
1500 - }
1501 - `,
1502 - },
1503 - {
1504 - code: normalizeIndent`
1505 - // Valid because functions created with useEffectEvent can be called in useInsertionEffect.
1506 - function MyComponent({ theme }) {
1507 - const onClick = useEffectEvent(() => {
1508 - showNotification(theme);
1509 - });
1510 - useInsertionEffect(() => {
1511 - onClick();
1512 - });
1513 - React.useInsertionEffect(() => {
1514 - onClick();
1515 - });
1516 - }
1517 - `,
1518 - },
1519 - {
1520 - code: normalizeIndent`
1521 - // Valid because functions created with useEffectEvent can be passed by reference in useLayoutEffect
1522 - // and useInsertionEffect.
1523 - function MyComponent({ theme }) {
1524 - const onClick = useEffectEvent(() => {
1525 - showNotification(theme);
1526 - });
1527 - const onClick2 = useEffectEvent(() => {
1528 - debounce(onClick);
1529 - debounce(() => onClick());
1530 - debounce(() => { onClick() });
1531 - deboucne(() => debounce(onClick));
1532 - });
1533 - useLayoutEffect(() => {
1534 - let id = setInterval(() => onClick(), 100);
1535 - return () => clearInterval(onClick);
1536 - }, []);
1537 - React.useLayoutEffect(() => {
1538 - let id = setInterval(() => onClick(), 100);
1539 - return () => clearInterval(onClick);
1540 - }, []);
1541 - useInsertionEffect(() => {
1542 - let id = setInterval(() => onClick(), 100);
1543 - return () => clearInterval(onClick);
1544 - }, []);
1545 - React.useInsertionEffect(() => {
1546 - let id = setInterval(() => onClick(), 100);
1547 - return () => clearInterval(onClick);
1548 - }, []);
1549 - return null;
1550 - }
1551 - `,
1552 - },
1553 - ];
1554 - allTests.invalid = [
1555 - ...allTests.invalid,
1547 {
1548 code: normalizeIndent`
1549 function MyComponent({ theme }) {
@@ -1659,8 +1650,8 @@ if (__EXPERIMENTAL__) {
1650 useEffectEventError('onClick', true),
1651 ],
1652 },
1662 - ];
1663 -}
1653 + ],
1654 +};
1655
1656 function conditionalError(hook, hasPreviousFinalizer = false) {
1657 return {
packages/eslint-plugin-react-hooks/src/rules/ExhaustiveDeps.ts
+1 -4
@@ -2122,10 +2122,7 @@ function isAncestorNodeOf(a: Node, b: Node): boolean {
2122 }
2123
2124 function isUseEffectEventIdentifier(node: Node): boolean {
2125 - if (__EXPERIMENTAL__) {
2126 - return node.type === 'Identifier' && node.name === 'useEffectEvent';
2127 - }
2128 - return false;
2125 + return node.type === 'Identifier' && node.name === 'useEffectEvent';
2126 }
2127
2128 function getUnknownDependenciesMessage(reactiveHookName: string): string {
packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts
+4 -6
@@ -20,7 +20,7 @@ import type {
20
21 // @ts-expect-error untyped module
22 import CodePathAnalyzer from '../code-path-analysis/code-path-analyzer';
23 -import { getAdditionalEffectHooksFromSettings } from '../shared/Utils';
23 +import {getAdditionalEffectHooksFromSettings} from '../shared/Utils';
24
25 /**
26 * Catch all identifiers that begin with "use" followed by an uppercase Latin
@@ -167,10 +167,7 @@ function isEffectIdentifier(node: Node, additionalHooks?: RegExp): boolean {
167 return false;
168 }
169 function isUseEffectEventIdentifier(node: Node): boolean {
170 - if (__EXPERIMENTAL__) {
171 - return node.type === 'Identifier' && node.name === 'useEffectEvent';
172 - }
173 - return false;
170 + return node.type === 'Identifier' && node.name === 'useEffectEvent';
171 }
172
173 function isUseIdentifier(node: Node): boolean {
@@ -200,7 +197,8 @@ const rule = {
197 create(context: Rule.RuleContext) {
198 const settings = context.settings || {};
199
203 - const additionalEffectHooks = getAdditionalEffectHooksFromSettings(settings);
200 + const additionalEffectHooks =
201 + getAdditionalEffectHooksFromSettings(settings);
202
203 let lastEffect: CallExpression | null = null;
204 const codePathReactHooksMapStack: Array<