Skip to content

Commit

Permalink
Unify experimental check for useEvent
Browse files Browse the repository at this point in the history
  • Loading branch information
poteto committed Sep 23, 2022
1 parent 76c096c commit 9ab6ebc
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 132 deletions.
257 changes: 133 additions & 124 deletions packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -406,87 +406,6 @@ const tests = {
const [myState, setMyState] = useState(null);
}
`,
`
// Valid because functions created with useEvent can be called in a useEffect.
function MyComponent({ theme }) {
const onClick = useEvent(() => {
showNotification(theme);
});
useEffect(() => {
onClick();
});
}
`,
`
// Valid because functions created with useEvent can be called in closures.
function MyComponent({ theme }) {
const onClick = useEvent(() => {
showNotification(theme);
});
return <Child onClick={() => onClick()}></Child>;
}
`,
`
// Valid because functions created with useEvent can be called in closures.
function MyComponent({ theme }) {
const onClick = useEvent(() => {
showNotification(theme);
});
const onClick2 = () => { onClick() };
const onClick3 = useCallback(() => onClick(), []);
return <>
<Child onClick={onClick2}></Child>
<Child onClick={onClick3}></Child>
</>;
}
`,
`
// Valid because functions created with useEvent can be passed by reference in useEffect
// and useEvent.
function MyComponent({ theme }) {
const onClick = useEvent(() => {
showNotification(theme);
});
const onClick2 = useEvent(() => {
debounce(onClick);
});
useEffect(() => {
let id = setInterval(onClick, 100);
return () => clearInterval(onClick);
}, []);
return <Child onClick={() => onClick2()} />
}
`,
`
const MyComponent = ({theme}) => {
const onClick = useEvent(() => {
showNotification(theme);
});
return <Child onClick={() => onClick()}></Child>;
};
`,
`
function MyComponent({ theme }) {
const notificationService = useNotifications();
const showNotification = useEvent((text) => {
notificationService.notify(theme, text);
});
const onClick = useEvent((text) => {
showNotification(text);
});
return <Child onClick={(text) => onClick(text)} />
}
`,
`
function MyComponent({ theme }) {
useEffect(() => {
onClick();
});
const onClick = useEvent(() => {
showNotification(theme);
});
}
`,
],
invalid: [
{
Expand Down Expand Up @@ -1052,66 +971,156 @@ const tests = {
`,
errors: [classError('useState')],
},
],
};

if (__EXPERIMENTAL__) {
tests.valid = [
...tests.valid,
`
// Valid because functions created with useEvent can be called in a useEffect.
function MyComponent({ theme }) {
const onClick = useEvent(() => {
showNotification(theme);
});
useEffect(() => {
onClick();
});
}
`,
`
// Valid because functions created with useEvent can be called in closures.
function MyComponent({ theme }) {
const onClick = useEvent(() => {
showNotification(theme);
});
return <Child onClick={() => onClick()}></Child>;
}
`,
`
// Valid because functions created with useEvent can be called in closures.
function MyComponent({ theme }) {
const onClick = useEvent(() => {
showNotification(theme);
});
const onClick2 = () => { onClick() };
const onClick3 = useCallback(() => onClick(), []);
return <>
<Child onClick={onClick2}></Child>
<Child onClick={onClick3}></Child>
</>;
}
`,
`
// Valid because functions created with useEvent can be passed by reference in useEffect
// and useEvent.
function MyComponent({ theme }) {
const onClick = useEvent(() => {
showNotification(theme);
});
const onClick2 = useEvent(() => {
debounce(onClick);
});
useEffect(() => {
let id = setInterval(onClick, 100);
return () => clearInterval(onClick);
}, []);
return <Child onClick={() => onClick2()} />
}
`,
`
const MyComponent = ({theme}) => {
const onClick = useEvent(() => {
showNotification(theme);
});
return <Child onClick={() => onClick()}></Child>;
};
`,
`
function MyComponent({ theme }) {
const notificationService = useNotifications();
const showNotification = useEvent((text) => {
notificationService.notify(theme, text);
});
const onClick = useEvent((text) => {
showNotification(text);
});
return <Child onClick={(text) => onClick(text)} />
}
`,
`
function MyComponent({ theme }) {
useEffect(() => {
onClick();
});
const onClick = useEvent(() => {
showNotification(theme);
});
}
`,
];
tests.invalid = [
...tests.invalid,
{
code: `
function MyComponent({ theme }) {
const onClick = useEvent(() => {
showNotification(theme);
});
return <Child onClick={onClick}></Child>;
}
`,
function MyComponent({ theme }) {
const onClick = useEvent(() => {
showNotification(theme);
});
return <Child onClick={onClick}></Child>;
}
`,
errors: [useEventError('onClick')],
},
{
code: `
// This should error even though it shares an identifier name with the below
function MyComponent({theme}) {
const onClick = useEvent(() => {
showNotification(theme)
});
return <Child onClick={onClick} />
}
// This should error even though it shares an identifier name with the below
function MyComponent({theme}) {
const onClick = useEvent(() => {
showNotification(theme)
});
return <Child onClick={onClick} />
}
// The useEvent function shares an identifier name with the above
function MyOtherComponent({theme}) {
const onClick = useEvent(() => {
showNotification(theme)
});
return <Child onClick={() => onClick()} />
}
`,
// The useEvent function shares an identifier name with the above
function MyOtherComponent({theme}) {
const onClick = useEvent(() => {
showNotification(theme)
});
return <Child onClick={() => onClick()} />
}
`,
errors: [{...useEventError('onClick'), line: 4}],
},
{
code: `
const MyComponent = ({ theme }) => {
const onClick = useEvent(() => {
showNotification(theme);
});
return <Child onClick={onClick}></Child>;
}
`,
const MyComponent = ({ theme }) => {
const onClick = useEvent(() => {
showNotification(theme);
});
return <Child onClick={onClick}></Child>;
}
`,
errors: [useEventError('onClick')],
},
{
code: `
// Invalid because onClick is being aliased to foo but not invoked
function MyComponent({ theme }) {
const onClick = useEvent(() => {
showNotification(theme);
});
let foo;
useEffect(() => {
foo = onClick;
});
return <Bar onClick={foo} />
}
`,
// Invalid because onClick is being aliased to foo but not invoked
function MyComponent({ theme }) {
const onClick = useEvent(() => {
showNotification(theme);
});
let foo;
useEffect(() => {
foo = onClick;
});
return <Bar onClick={foo} />
}
`,
errors: [useEventError('onClick')],
},
],
};
];
}

function conditionalError(hook, hasPreviousFinalizer = false) {
return {
Expand Down
12 changes: 8 additions & 4 deletions packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js
Original file line number Diff line number Diff line change
Expand Up @@ -225,10 +225,7 @@ export default {
if (name === 'useRef' && id.type === 'Identifier') {
// useRef() return value is stable.
return true;
} else if (
name === 'experimental_useEvent' &&
id.type === 'Identifier'
) {
} else if (isUseEventIdentifier(callee) && id.type === 'Identifier') {
// useEvent() return value is stable.
return true;
} else if (name === 'useState' || name === 'useReducer') {
Expand Down Expand Up @@ -1827,3 +1824,10 @@ function isSameIdentifier(a, b) {
function isAncestorNodeOf(a, b) {
return a.range[0] <= b.range[0] && a.range[1] >= b.range[1];
}

function isUseEventIdentifier(node) {
if (__EXPERIMENTAL__) {
return node.type === 'Identifier' && node.name === 'useEvent';
}
return false;
}
8 changes: 4 additions & 4 deletions packages/eslint-plugin-react-hooks/src/RulesOfHooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,10 @@ function isInsideComponentOrHook(node) {
}

function isUseEventIdentifier(node) {
return (
node.type === 'Identifier' &&
(node.name === 'useEvent' || node.name === 'experimental_useEvent')
);
if (__EXPERIMENTAL__) {
return node.type === 'Identifier' && node.name === 'useEvent';
}
return false;
}

export default {
Expand Down

0 comments on commit 9ab6ebc

Please sign in to comment.