Skip to content

Commit 6f6ba50

Browse files
authored
chore(ws): enforce named imports for react hooks (#414)
Signed-off-by: paulovmr <[email protected]>
1 parent 6b2c2bb commit 6f6ba50

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+269
-219
lines changed

workspaces/frontend/.eslintrc renamed to workspaces/frontend/.eslintrc.js

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
{
1+
const noReactHookNamespace = require('./eslint-local-rules/no-react-hook-namespace');
2+
3+
module.exports = {
24
"parser": "@typescript-eslint/parser",
35
"env": {
46
"browser": true,
@@ -216,7 +218,8 @@
216218
"no-useless-return": "error",
217219
"symbol-description": "error",
218220
"yoda": "error",
219-
"func-names": "warn"
221+
"func-names": "warn",
222+
"no-react-hook-namespace": "error"
220223
},
221224
"overrides": [
222225
{
@@ -262,6 +265,12 @@
262265
}
263266
]
264267
}
268+
},
269+
{
270+
files: ['**/*.{js,jsx,ts,tsx}'],
271+
rules: {
272+
'no-react-hook-namespace': 'error',
273+
},
265274
}
266275
]
267-
}
276+
};
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
module.exports = {
2+
meta: {
3+
type: 'problem',
4+
docs: {
5+
description: 'Disallow using React hooks through React namespace',
6+
},
7+
messages: {
8+
avoidNamespaceHook: 'Import React hook "{{hook}}" directly instead of using React.{{hook}}.',
9+
},
10+
schema: [],
11+
},
12+
create(context) {
13+
const hooks = new Set([
14+
'useState', 'useEffect', 'useContext', 'useReducer',
15+
'useCallback', 'useMemo', 'useRef', 'useLayoutEffect',
16+
'useImperativeHandle', 'useDebugValue', 'useDeferredValue',
17+
'useTransition', 'useId', 'useSyncExternalStore',
18+
]);
19+
return {
20+
MemberExpression(node) {
21+
if (
22+
node.object?.name === 'React' &&
23+
hooks.has(node.property?.name)
24+
) {
25+
context.report({
26+
node,
27+
messageId: 'avoidNamespaceHook',
28+
data: { hook: node.property.name },
29+
});
30+
}
31+
},
32+
};
33+
},
34+
};

workspaces/frontend/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@
2424
"test:unit": "npm run test:jest -- --silent",
2525
"test:watch": "jest --watch",
2626
"test:coverage": "jest --coverage",
27-
"test:fix": "eslint --ext .js,.ts,.jsx,.tsx ./src --fix",
28-
"test:lint": "eslint --max-warnings 0 --ext .js,.ts,.jsx,.tsx ./src",
27+
"test:fix": "eslint --rulesdir eslint-local-rules --ext .js,.ts,.jsx,.tsx ./src --fix",
28+
"test:lint": "eslint --rulesdir eslint-local-rules --max-warnings 0 --ext .js,.ts,.jsx,.tsx ./src",
2929
"cypress:open": "cypress open --project src/__tests__/cypress",
3030
"cypress:open:mock": "CY_MOCK=1 CY_WS_PORT=9002 npm run cypress:open -- ",
3131
"cypress:run": "cypress run -b chrome --project src/__tests__/cypress",

workspaces/frontend/src/__tests__/unit/testUtils/hooks.spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
import * as React from 'react';
1+
import { useEffect, useRef, useState } from 'react';
22
import { createComparativeValue, renderHook, standardUseFetchState, testHook } from './hooks';
33

44
const useSayHello = (who: string, showCount = false) => {
5-
const countRef = React.useRef(0);
5+
const countRef = useRef(0);
66
countRef.current++;
77
return `Hello ${who}!${showCount && countRef.current > 1 ? ` x${countRef.current}` : ''}`;
88
};
99

1010
const useSayHelloDelayed = (who: string, delay = 0) => {
11-
const [speech, setSpeech] = React.useState('');
12-
React.useEffect(() => {
11+
const [speech, setSpeech] = useState('');
12+
useEffect(() => {
1313
const handle = setTimeout(() => setSpeech(`Hello ${who}!`), delay);
1414
return () => clearTimeout(handle);
1515
}, [who, delay]);

workspaces/frontend/src/app/App.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import * as React from 'react';
1+
import React, { useEffect } from 'react';
22
import '@patternfly/react-core/dist/styles/base.css';
33
import './app.css';
44
import {
@@ -26,7 +26,7 @@ import { isMUITheme, Theme } from './const';
2626
import { BrowserStorageContextProvider } from './context/BrowserStorageContext';
2727

2828
const App: React.FC = () => {
29-
React.useEffect(() => {
29+
useEffect(() => {
3030
// Apply the theme based on the value of STYLE_THEME
3131
if (isMUITheme()) {
3232
document.documentElement.classList.add(Theme.MUI);

workspaces/frontend/src/app/AppRoutes.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import * as React from 'react';
1+
import React from 'react';
22
import { Route, Routes, Navigate } from 'react-router-dom';
33
import { AppRoutePaths } from '~/app/routes';
44
import { WorkspaceForm } from '~/app/pages/Workspaces/Form/WorkspaceForm';

workspaces/frontend/src/app/EnsureAPIAvailability.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import * as React from 'react';
1+
import React from 'react';
22
import { Bullseye, Spinner } from '@patternfly/react-core';
33
import { useNotebookAPI } from './hooks/useNotebookAPI';
44

workspaces/frontend/src/app/NavSidebar.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import * as React from 'react';
1+
import React, { useState } from 'react';
22
import { NavLink, useLocation } from 'react-router-dom';
33
import {
44
Brand,
@@ -29,7 +29,7 @@ const NavHref: React.FC<{ item: NavDataHref }> = ({ item }) => {
2929

3030
const NavGroup: React.FC<{ item: NavDataGroup }> = ({ item }) => {
3131
const { children } = item;
32-
const [expanded, setExpanded] = React.useState(false);
32+
const [expanded, setExpanded] = useState(false);
3333

3434
return (
3535
<NavExpandable

workspaces/frontend/src/app/components/ThemeAwareSearchInput.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import * as React from 'react';
1+
import React from 'react';
22
import { SearchInput, SearchInputProps, TextInput } from '@patternfly/react-core';
33
import FormFieldset from 'app/components/FormFieldset';
44
import { isMUITheme } from 'app/const';

workspaces/frontend/src/app/context/BrowserStorageContext.tsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1-
import React, { createContext, useCallback, useContext, useEffect } from 'react';
1+
import React, {
2+
createContext,
3+
useCallback,
4+
useContext,
5+
useEffect,
6+
useMemo,
7+
useRef,
8+
useState,
9+
} from 'react';
210

311
export interface BrowserStorageContextType {
412
getValue: (key: string) => unknown;
@@ -17,8 +25,8 @@ const BrowserStorageContext = createContext<BrowserStorageContextType>({
1725
export const BrowserStorageContextProvider: React.FC<BrowserStorageContextProviderProps> = ({
1826
children,
1927
}) => {
20-
const [values, setValues] = React.useState<{ [key: string]: unknown }>({});
21-
const valuesRef = React.useRef(values);
28+
const [values, setValues] = useState<{ [key: string]: unknown }>({});
29+
const valuesRef = useRef(values);
2230
useEffect(() => {
2331
valuesRef.current = values;
2432
}, [values]);
@@ -49,7 +57,7 @@ export const BrowserStorageContextProvider: React.FC<BrowserStorageContextProvid
4957
);
5058

5159
// eslint-disable-next-line react-hooks/exhaustive-deps
52-
const contextValue = React.useMemo(() => ({ getValue, setValue }), [getValue, setValue, values]);
60+
const contextValue = useMemo(() => ({ getValue, setValue }), [getValue, setValue, values]);
5361

5462
return (
5563
<BrowserStorageContext.Provider value={contextValue}>{children}</BrowserStorageContext.Provider>

0 commit comments

Comments
 (0)