Skip to content

Commit 3633d61

Browse files
authored
[1/2] [@graphiql/react] compile source code with react-compiler, remove useMemo and useCallback usages (#3821)
* aa * aa * aa * aa * aa * aa * aa * aa * aa * aa * aa * aa * aa * [skip ci] * fix * migrate `graphiql` from `jest` to `vitest` * add .js extension * add .js extension * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd
1 parent efde1a0 commit 3633d61

Some content is hidden

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

43 files changed

+1128
-827
lines changed

.changeset/hip-cycles-agree.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@graphiql/react': minor
3+
---
4+
5+
compile source code with react-compiler, remove `useMemo` and `useCallback` usages

.eslintrc.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,21 @@ module.exports = {
453453
'promise/prefer-await-to-then': 'error',
454454
},
455455
},
456+
{
457+
files: ['packages/graphiql-react/**'],
458+
plugins: ['react-compiler'],
459+
rules: {
460+
'@typescript-eslint/no-restricted-imports': [
461+
'error',
462+
...RESTRICTED_IMPORTS,
463+
{
464+
name: 'react',
465+
importNames: ['memo', 'useCallback', 'useMemo'],
466+
},
467+
],
468+
'react-compiler/react-compiler': 'error',
469+
},
470+
},
456471
{
457472
// Monaco-GraphQL rules
458473
files: ['packages/monaco-graphql/**'],

package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@
7878
"gen-agenda": "wgutils agenda gen"
7979
},
8080
"dependencies": {
81+
"eslint-plugin-react-compiler": "19.0.0-beta-37ed2a7-20241206",
8182
"graphql-http": "^1.22.1",
8283
"@babel/cli": "^7.21.0",
8384
"@babel/core": "^7.21.0",
@@ -109,16 +110,16 @@
109110
"concurrently": "^7.0.0",
110111
"copy": "^0.3.2",
111112
"cspell": "^5.15.2",
112-
"eslint": "^9.7.0",
113+
"eslint": "9.14.0",
113114
"eslint-config-prettier": "^9.1.0",
114115
"eslint-import-resolver-typescript": "^3.6.1",
115116
"eslint-plugin-cypress": "^3.4.0",
116117
"eslint-plugin-import-x": "^3.1.0",
117118
"eslint-plugin-jest": "^28.6.0",
118119
"eslint-plugin-mdx": "^3.1.5",
119120
"eslint-plugin-promise": "^7.0.0",
120-
"eslint-plugin-react": "^7.35.0",
121-
"eslint-plugin-react-hooks": "^5.1.0-rc-76002254-20240724",
121+
"eslint-plugin-react": "^7.37.2",
122+
"eslint-plugin-react-hooks": "^5.1.0",
122123
"eslint-plugin-sonar": "^0.14.1",
123124
"eslint-plugin-sonarjs": "^1.0.4",
124125
"eslint-plugin-unicorn": "^55.0.0",

packages/graphiql-react/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"react-dom": "^16.8.0 || ^17 || ^18"
4949
},
5050
"dependencies": {
51+
"react-compiler-runtime": "19.0.0-beta-37ed2a7-20241206",
5152
"@graphiql/toolkit": "^0.11.0",
5253
"@headlessui/react": "^1.7.15",
5354
"@radix-ui/react-dialog": "^1.0.4",
@@ -66,6 +67,7 @@
6667
"set-value": "^4.1.0"
6768
},
6869
"devDependencies": {
70+
"babel-plugin-react-compiler": "19.0.0-beta-37ed2a7-20241206",
6971
"@types/react-dom": "^18.3.1",
7072
"@babel/helper-string-parser": "^7.19.4",
7173
"@testing-library/dom": "^10.4.0",

packages/graphiql-react/src/editor/common.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
'use no memo';
2+
13
import { KeyMap } from './types';
24
import { isMacOs } from '../utility/is-macos';
35

packages/graphiql-react/src/editor/completion.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
'use no memo';
2+
13
import type { Editor, EditorChange } from 'codemirror';
24
import type { IHint } from 'codemirror-graphql/hint';
35
import {

packages/graphiql-react/src/editor/components/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
'use no memo';
2+
13
export { HeaderEditor } from './header-editor';
24
export { ImagePreview } from './image-preview';
35
export { QueryEditor } from './query-editor';

packages/graphiql-react/src/editor/context.tsx

Lines changed: 84 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,7 @@ import {
77
visit,
88
} from 'graphql';
99
import { VariableToType } from 'graphql-language-service';
10-
import {
11-
ReactNode,
12-
useCallback,
13-
useEffect,
14-
useMemo,
15-
useRef,
16-
useState,
17-
} from 'react';
10+
import { ReactNode, useEffect, useRef, useState } from 'react';
1811

1912
import { useStorageContext } from '../storage';
2013
import { createContextHook, createNullableContext } from '../utility/context';
@@ -329,7 +322,7 @@ export function EditorContextProvider(props: EditorContextProviderProps) {
329322

330323
const [tabState, setTabState] = useState<TabsState>(initialState.tabState);
331324

332-
const setShouldPersistHeaders = useCallback(
325+
const setShouldPersistHeaders = // eslint-disable-line react-hooks/exhaustive-deps -- false positive, function is optimized by react-compiler, no need to wrap with useCallback
333326
(persist: boolean) => {
334327
if (persist) {
335328
storage?.set(STORAGE_KEY_HEADERS, headerEditor?.getValue() ?? '');
@@ -341,9 +334,7 @@ export function EditorContextProvider(props: EditorContextProviderProps) {
341334
}
342335
setShouldPersistHeadersInternal(persist);
343336
storage?.set(PERSIST_HEADERS_STORAGE_KEY, persist.toString());
344-
},
345-
[storage, tabState, headerEditor],
346-
);
337+
};
347338

348339
const lastShouldPersistHeadersProp = useRef<boolean | undefined>();
349340
useEffect(() => {
@@ -369,7 +360,7 @@ export function EditorContextProvider(props: EditorContextProviderProps) {
369360
defaultHeaders,
370361
});
371362

372-
const addTab = useCallback<EditorContextType['addTab']>(() => {
363+
const addTab: EditorContextType['addTab'] = () => {
373364
setTabState(current => {
374365
// Make sure the current tab stores the latest values
375366
const updatedValues = synchronizeActiveTabValues(current);
@@ -388,93 +379,71 @@ export function EditorContextProvider(props: EditorContextProviderProps) {
388379
onTabChange?.(updated);
389380
return updated;
390381
});
391-
}, [
392-
defaultHeaders,
393-
defaultQuery,
394-
onTabChange,
395-
setEditorValues,
396-
storeTabs,
397-
synchronizeActiveTabValues,
398-
]);
399-
400-
const changeTab = useCallback<EditorContextType['changeTab']>(
401-
index => {
402-
setTabState(current => {
403-
const updated = {
404-
...current,
405-
activeTabIndex: index,
406-
};
407-
storeTabs(updated);
408-
setEditorValues(updated.tabs[updated.activeTabIndex]);
409-
onTabChange?.(updated);
410-
return updated;
411-
});
412-
},
413-
[onTabChange, setEditorValues, storeTabs],
414-
);
382+
};
415383

416-
const moveTab = useCallback<EditorContextType['moveTab']>(
417-
newOrder => {
418-
setTabState(current => {
419-
const activeTab = current.tabs[current.activeTabIndex];
420-
const updated = {
421-
tabs: newOrder,
422-
activeTabIndex: newOrder.indexOf(activeTab),
423-
};
424-
storeTabs(updated);
425-
setEditorValues(updated.tabs[updated.activeTabIndex]);
426-
onTabChange?.(updated);
427-
return updated;
428-
});
429-
},
430-
[onTabChange, setEditorValues, storeTabs],
431-
);
384+
const changeTab: EditorContextType['changeTab'] = index => {
385+
setTabState(current => {
386+
const updated = {
387+
...current,
388+
activeTabIndex: index,
389+
};
390+
storeTabs(updated);
391+
setEditorValues(updated.tabs[updated.activeTabIndex]);
392+
onTabChange?.(updated);
393+
return updated;
394+
});
395+
};
432396

433-
const closeTab = useCallback<EditorContextType['closeTab']>(
434-
index => {
435-
setTabState(current => {
436-
const updated = {
437-
tabs: current.tabs.filter((_tab, i) => index !== i),
438-
activeTabIndex: Math.max(current.activeTabIndex - 1, 0),
439-
};
440-
storeTabs(updated);
441-
setEditorValues(updated.tabs[updated.activeTabIndex]);
442-
onTabChange?.(updated);
443-
return updated;
444-
});
445-
},
446-
[onTabChange, setEditorValues, storeTabs],
447-
);
397+
const moveTab: EditorContextType['moveTab'] = newOrder => {
398+
setTabState(current => {
399+
const activeTab = current.tabs[current.activeTabIndex];
400+
const updated = {
401+
tabs: newOrder,
402+
activeTabIndex: newOrder.indexOf(activeTab),
403+
};
404+
storeTabs(updated);
405+
setEditorValues(updated.tabs[updated.activeTabIndex]);
406+
onTabChange?.(updated);
407+
return updated;
408+
});
409+
};
410+
411+
const closeTab: EditorContextType['closeTab'] = index => {
412+
setTabState(current => {
413+
const updated = {
414+
tabs: current.tabs.filter((_tab, i) => index !== i),
415+
activeTabIndex: Math.max(current.activeTabIndex - 1, 0),
416+
};
417+
storeTabs(updated);
418+
setEditorValues(updated.tabs[updated.activeTabIndex]);
419+
onTabChange?.(updated);
420+
return updated;
421+
});
422+
};
448423

449-
const updateActiveTabValues = useCallback<
450-
EditorContextType['updateActiveTabValues']
451-
>(
424+
const updateActiveTabValues: EditorContextType['updateActiveTabValues'] =
452425
partialTab => {
453426
setTabState(current => {
454427
const updated = setPropertiesInActiveTab(current, partialTab);
455428
storeTabs(updated);
456429
onTabChange?.(updated);
457430
return updated;
458431
});
459-
},
460-
[onTabChange, storeTabs],
461-
);
432+
};
462433

463434
const { onEditOperationName } = props;
464-
const setOperationName = useCallback<EditorContextType['setOperationName']>(
435+
const setOperationName: EditorContextType['setOperationName'] =
465436
operationName => {
466437
if (!queryEditor) {
467438
return;
468439
}
469440

470-
queryEditor.operationName = operationName;
441+
updateQueryEditor(queryEditor, operationName);
471442
updateActiveTabValues({ operationName });
472443
onEditOperationName?.(operationName);
473-
},
474-
[onEditOperationName, queryEditor, updateActiveTabValues],
475-
);
444+
};
476445

477-
const externalFragments = useMemo(() => {
446+
const externalFragments = (() => {
478447
const map = new Map<string, FragmentDefinitionNode>();
479448
if (Array.isArray(props.externalFragments)) {
480449
for (const fragment of props.externalFragments) {
@@ -492,74 +461,54 @@ export function EditorContextProvider(props: EditorContextProviderProps) {
492461
);
493462
}
494463
return map;
495-
}, [props.externalFragments]);
464+
})();
496465

497-
const validationRules = useMemo(
498-
() => props.validationRules || [],
499-
[props.validationRules],
500-
);
466+
const validationRules = props.validationRules || [];
501467

502-
const value = useMemo<EditorContextType>(
503-
() => ({
504-
...tabState,
505-
addTab,
506-
changeTab,
507-
moveTab,
508-
closeTab,
509-
updateActiveTabValues,
510-
511-
headerEditor,
512-
queryEditor,
513-
responseEditor,
514-
variableEditor,
515-
setHeaderEditor,
516-
setQueryEditor,
517-
setResponseEditor,
518-
setVariableEditor,
519-
520-
setOperationName,
521-
522-
initialQuery: initialState.query,
523-
initialVariables: initialState.variables,
524-
initialHeaders: initialState.headers,
525-
initialResponse: initialState.response,
526-
527-
externalFragments,
528-
validationRules,
468+
const value: EditorContextType = {
469+
...tabState,
470+
addTab,
471+
changeTab,
472+
moveTab,
473+
closeTab,
474+
updateActiveTabValues,
529475

530-
shouldPersistHeaders,
531-
setShouldPersistHeaders,
532-
}),
533-
[
534-
tabState,
535-
addTab,
536-
changeTab,
537-
moveTab,
538-
closeTab,
539-
updateActiveTabValues,
540-
541-
headerEditor,
542-
queryEditor,
543-
responseEditor,
544-
variableEditor,
476+
headerEditor,
477+
queryEditor,
478+
responseEditor,
479+
variableEditor,
480+
setHeaderEditor,
481+
setQueryEditor,
482+
setResponseEditor,
483+
setVariableEditor,
545484

546-
setOperationName,
485+
setOperationName,
547486

548-
initialState,
487+
initialQuery: initialState.query,
488+
initialVariables: initialState.variables,
489+
initialHeaders: initialState.headers,
490+
initialResponse: initialState.response,
549491

550-
externalFragments,
551-
validationRules,
492+
externalFragments,
493+
validationRules,
552494

553-
shouldPersistHeaders,
554-
setShouldPersistHeaders,
555-
],
556-
);
495+
shouldPersistHeaders,
496+
setShouldPersistHeaders,
497+
};
557498

558499
return (
559500
<EditorContext.Provider value={value}>{children}</EditorContext.Provider>
560501
);
561502
}
562503

504+
// To make react-compiler happy, otherwise it fails due mutating props
505+
function updateQueryEditor(
506+
queryEditor: CodeMirrorEditorWithOperationFacts,
507+
operationName: string,
508+
) {
509+
queryEditor.operationName = operationName;
510+
}
511+
563512
export const useEditorContext = createContextHook(EditorContext);
564513

565514
const PERSIST_HEADERS_STORAGE_KEY = 'shouldPersistHeaders';

0 commit comments

Comments
 (0)