From ecb12f26d06080577ab88b750a6553b1511fe224 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Mon, 27 Feb 2023 15:55:50 +0100 Subject: [PATCH 1/3] avoid `useMemo` with empty array `[]` since React can't guarantee stable reference, + lint restrict syntax for future mistakes --- .changeset/two-icons-rhyme.md | 6 ++ .eslintrc.js | 11 +++- .../src/index.tsx | 52 ++++++++-------- .../graphiql-plugin-explorer/src/index.tsx | 60 +++++++++---------- 4 files changed, 71 insertions(+), 58 deletions(-) create mode 100644 .changeset/two-icons-rhyme.md diff --git a/.changeset/two-icons-rhyme.md b/.changeset/two-icons-rhyme.md new file mode 100644 index 00000000000..d5b93d6adfc --- /dev/null +++ b/.changeset/two-icons-rhyme.md @@ -0,0 +1,6 @@ +--- +'@graphiql/plugin-code-exporter': patch +'@graphiql/plugin-explorer': patch +--- + +avoid `useMemo` with empty array `[]` since React can't guarantee stable reference, + lint restrict syntax for future mistakes diff --git a/.eslintrc.js b/.eslintrc.js index d7d58c365bf..90d4997b9b2 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -185,7 +185,16 @@ module.exports = { 'no-nested-ternary': 0, 'no-new-object': 1, 'no-plusplus': 0, - 'no-restricted-syntax': 0, + 'no-restricted-syntax': [ + 'error', + { + // ❌ useMemo(…, []) + selector: + 'CallExpression[callee.name=useMemo][arguments.1.type=ArrayExpression][arguments.1.elements.length=0]', + message: + "`useMemo` with an empty dependency array can't provide a stable reference, use `useRef` instead.", + }, + ], 'no-ternary': 0, 'no-underscore-dangle': 0, 'no-unneeded-ternary': 0, diff --git a/packages/graphiql-plugin-code-exporter/src/index.tsx b/packages/graphiql-plugin-code-exporter/src/index.tsx index 7eadddbfd31..432e2930c76 100644 --- a/packages/graphiql-plugin-code-exporter/src/index.tsx +++ b/packages/graphiql-plugin-code-exporter/src/index.tsx @@ -1,5 +1,5 @@ import type { GraphiQLPlugin } from '@graphiql/react'; -import { useMemo, useRef } from 'react'; +import { useRef } from 'react'; import GraphiQLCodeExporter, { GraphiQLCodeExporterProps, } from 'graphiql-code-exporter'; @@ -10,31 +10,29 @@ import './index.css'; export function useExporterPlugin(props: GraphiQLCodeExporterProps) { const propsRef = useRef(props); propsRef.current = props; - return useMemo( - () => ({ - title: 'GraphiQL Code Exporter', - icon: () => ( - - - - ), - content: () => ( - (); + pluginRef.current ||= { + title: 'GraphiQL Code Exporter', + icon: () => ( + + - ), - }), - [], - ); + + ), + content: () => ( + + ), + }; + + return pluginRef.current; } diff --git a/packages/graphiql-plugin-explorer/src/index.tsx b/packages/graphiql-plugin-explorer/src/index.tsx index 7689cdfdf0c..97ce94e2db5 100644 --- a/packages/graphiql-plugin-explorer/src/index.tsx +++ b/packages/graphiql-plugin-explorer/src/index.tsx @@ -5,7 +5,7 @@ import { useSchemaContext, } from '@graphiql/react'; import GraphiQLExplorer, { GraphiQLExplorerProps } from 'graphiql-explorer'; -import { useMemo, useRef } from 'react'; +import { useRef } from 'react'; import './graphiql-explorer.d.ts'; import './index.css'; @@ -129,33 +129,33 @@ function ExplorerPlugin(props: GraphiQLExplorerProps) { export function useExplorerPlugin(props: GraphiQLExplorerProps) { const propsRef = useRef(props); propsRef.current = props; - return useMemo( - () => ({ - title: 'GraphiQL Explorer', - icon: () => ( - - - - - - ), - content: () => , - }), - [], - ); + + const pluginRef = useRef(); + pluginRef.current ||= { + title: 'GraphiQL Explorer', + icon: () => ( + + + + + + ), + content: () => , + }; + return pluginRef.current; } From addc463c9984b19acad20289ac71ecde0fa97902 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Sun, 5 Mar 2023 11:48:04 +0100 Subject: [PATCH 2/3] fix migration --- docs/migration/graphiql-2.0.0.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/migration/graphiql-2.0.0.md b/docs/migration/graphiql-2.0.0.md index 9add8e73082..d64d4fe6fcf 100644 --- a/docs/migration/graphiql-2.0.0.md +++ b/docs/migration/graphiql-2.0.0.md @@ -100,7 +100,7 @@ been removed and where you can find them now: The `GraphiQL` component in `graphiql@1.x` was a class component. That allowed easy access to its props, state and methods by attaching a ref to it like so: -```jsx +```tsx import { createGraphiQLFetcher } from '@graphiql/toolkit'; import { GraphiQL } from 'graphiql'; import React from 'react'; @@ -228,8 +228,8 @@ components that can be passed as children to the `GraphiQL` components and override certain parts of the UI: - `GraphiQL.Logo`: Overrides the "logo" at the top right of the screen. By - default it contains the text "Graph*i*QL". -- `GraphiQL.Toolbar`: Overrides the toolbar next to the query editor. By default + default, it contains the text "Graph*i*QL". +- `GraphiQL.Toolbar`: Overrides the toolbar next to the query editor. By default, if contains buttons for prettifying the current editor contents, merging fragment definitions into the operation definition and copying the contents of the query editor to the clipboard. Note that the default buttons will not be @@ -237,7 +237,7 @@ override certain parts of the UI: the children you pass to `GraphiQL.Toolbar`. The execute button will always be shown. If you want to keep the default buttons and add additional buttons you can use the `toolbar` prop. -- `GraphiQL.Footer`: Adds a section below the response editor. By default this +- `GraphiQL.Footer`: Adds a section below the response editor. By default, this won't show up in the UI. Here is a list of all the static properties that have been removed and their From f56f610d34dcd227c0f0b98b630c54780ed39676 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Sun, 5 Mar 2023 11:48:46 +0100 Subject: [PATCH 3/3] prettify --- docs/migration/graphiql-2.0.0.md | 16 ++++++++-------- packages/graphiql/README.md | 5 ++++- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/docs/migration/graphiql-2.0.0.md b/docs/migration/graphiql-2.0.0.md index d64d4fe6fcf..939f22c75a8 100644 --- a/docs/migration/graphiql-2.0.0.md +++ b/docs/migration/graphiql-2.0.0.md @@ -229,14 +229,14 @@ override certain parts of the UI: - `GraphiQL.Logo`: Overrides the "logo" at the top right of the screen. By default, it contains the text "Graph*i*QL". -- `GraphiQL.Toolbar`: Overrides the toolbar next to the query editor. By default, - if contains buttons for prettifying the current editor contents, merging - fragment definitions into the operation definition and copying the contents of - the query editor to the clipboard. Note that the default buttons will not be - shown when passing this component as child to `GraphiQL`, instead it will show - the children you pass to `GraphiQL.Toolbar`. The execute button will always be - shown. If you want to keep the default buttons and add additional buttons you - can use the `toolbar` prop. +- `GraphiQL.Toolbar`: Overrides the toolbar next to the query editor. By + default, if contains buttons for prettifying the current editor contents, + merging fragment definitions into the operation definition and copying the + contents of the query editor to the clipboard. Note that the default buttons + will not be shown when passing this component as child to `GraphiQL`, instead + it will show the children you pass to `GraphiQL.Toolbar`. The execute button + will always be shown. If you want to keep the default buttons and add + additional buttons you can use the `toolbar` prop. - `GraphiQL.Footer`: Adds a section below the response editor. By default, this won't show up in the UI. diff --git a/packages/graphiql/README.md b/packages/graphiql/README.md index da5ad15efb2..3be8276dca2 100644 --- a/packages/graphiql/README.md +++ b/packages/graphiql/README.md @@ -111,7 +111,10 @@ import 'graphiql/graphiql.css'; const fetcher = createGraphiQLFetcher({ url: 'https://my.backend/graphql' }); -ReactDOM.render(, document.getElementById('root')); +ReactDOM.render( + , + document.getElementById('root'), +); ``` ### Using as UMD bundle over CDN (Unpkg, JSDelivr, etc)