Skip to content

Commit 9b54581

Browse files
authored
fix multiple GraphiQL instances, append an incremented number for operation, request headers, variables and reponse URI. (#4052)
* upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * fix react 19 * try this * upd * Update packages/graphiql/cypress/support/commands.ts
1 parent 2c0586d commit 9b54581

File tree

22 files changed

+180
-101
lines changed

22 files changed

+180
-101
lines changed

.changeset/five-ghosts-bathe.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
'@graphiql/react': patch
3+
'graphiql': patch
4+
---
5+
6+
fix multiple GraphiQL instances, suffix a unique id for operation, request headers, variables and response URI.
7+
8+
E.g., the first GraphiQL instance will have:
9+
- `1-operation.graphql`
10+
- `1-request-headers.json`
11+
- `1-variables.json`
12+
- `1-response.json`
13+
14+
The 2nd instance will have:
15+
16+
- `2-operation.graphql`
17+
- `2-request-headers.json`
18+
- `2-variables.json`
19+
- `2-response.json`
20+
21+
etc.

.changeset/proud-bottles-punch.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@
55

66
- use `allowTrailingComma` option in jsonc parser to make `tryParseJsonObject` sync
77
- parse introspection headers with jsonc parser
8-
- use prettier format for query editor since we already use prettier for jsonc editors
8+
- use prettier format for operation editor since we already use prettier for jsonc editors

docs/migration/graphiql-2.0.0.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,10 +229,10 @@ override certain parts of the UI:
229229

230230
- `GraphiQL.Logo`: Overrides the "logo" at the top right of the screen. By
231231
default, it contains the text "Graph*i*QL".
232-
- `GraphiQL.Toolbar`: Overrides the toolbar next to the query editor. By
232+
- `GraphiQL.Toolbar`: Overrides the toolbar next to the operation editor. By
233233
default, if contains buttons for prettifying the current editor contents,
234234
merging fragment definitions into the operation definition and copying the
235-
contents of the query editor to the clipboard. Note that the default buttons
235+
contents of the operation editor to the clipboard. Note that the default buttons
236236
will not be shown when passing this component as child to `GraphiQL`, instead
237237
it will show the children you pass to `GraphiQL.Toolbar`. The execute button
238238
will always be shown. If you want to keep the default buttons and add

docs/migration/graphiql-5.0.0.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ function App() {
132132
133133
- Migration from Codemirror to [Monaco Editor](https://github.com/microsoft/monaco-editor)
134134
- Replacing `codemirror-graphql` with [`monaco-graphql`](../../packages/monaco-graphql)
135-
- Clicking on a reference in the query editor now works by holding `Cmd` on macOS or `Ctrl` on Windows/Linux
135+
- Clicking on a reference in the operation editor now works by holding `Cmd` on macOS or `Ctrl` on Windows/Linux
136136
- Support for comments in **Variables** and **Headers** editors
137137
- Added new examples: [**GraphiQL x Vite**](../../examples/graphiql-vite) and [**GraphiQL x Next.js**](../../examples/graphiql-nextjs)
138138
- Removed examples: **GraphiQL x Parcel** and **GraphiQL x Create React App**
@@ -149,7 +149,7 @@ function App() {
149149
>
150150
> If you used `query`, `variables` and `headers` in integration tests, you can use the new `initialQuery`,
151151
> `initialVariables` and `initialHeaders` props instead. These props will only be used for the first tab.
152-
> When opening more tabs, the query editor will start out empty.
152+
> When opening more tabs, the operation editor will start out empty.
153153
154154
- Added new props
155155
- `initialQuery`
@@ -163,14 +163,14 @@ function App() {
163163

164164
> [!IMPORTANT]
165165
>
166-
> Clicking on a reference in the Query editor now works by holding `Cmd` on macOS or `Ctrl` on Windows/Linux.
166+
> Clicking on a reference in the operation editor now works by holding `Cmd` on macOS or `Ctrl` on Windows/Linux.
167167
168168
- `usePrettifyEditors`, `useCopyQuery`, `useMergeQuery`, `useExecutionContext`, `usePluginContext`, `useSchemaContext`, `useStorageContext` hooks are deprecated.
169169
- Add new `useGraphiQL` and `useGraphiQLActions` hooks instead. See updated [README](../../packages/graphiql-react/README.md#available-stores) for more details about them.
170170
- remove `useSynchronizeValue` hook
171171
- fix `defaultQuery` with empty string does not result in an empty default query
172-
- fix `defaultQuery`, when is set will only be used for the first tab. When opening more tabs, the query editor will start out empty
173-
- fix execute query shortcut in query editor, run it even there are no operations in query editor
172+
- fix `defaultQuery`, when is set will only be used for the first tab. When opening more tabs, the operation editor will start out empty
173+
- fix execute query shortcut in operation editor, run it even there are no operations in the operation editor
174174
- fix plugin store, save last opened plugin in storage
175175
- remove `onClickReference` from variable editor
176176
- fix shortcut text per OS for default query and in run query in execute query button's tooltip

packages/graphiql-react/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ function MyGraphQLIDE() {
4444
```
4545

4646
Inside the provider you can now use any UI component provided by
47-
`@graphiql/react`. For example, you can render a query editor like this:
47+
`@graphiql/react`. For example, you can render an operation editor like this:
4848

4949
```jsx
5050
import { QueryEditor } from '@graphiql/react';

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ export { ExecuteButton } from './execute-button';
22
export { ToolbarButton } from './toolbar-button';
33
export { ToolbarMenu } from './toolbar-menu';
44

5-
export { HeaderEditor } from './header-editor';
5+
export { RequestHeadersEditor as HeaderEditor } from './request-headers-editor';
66
export { ImagePreview } from './image-preview';
77
export { GraphiQLProvider, useGraphiQL, useGraphiQLActions } from './provider';
8-
export { QueryEditor } from './query-editor';
8+
export { OperationEditor as QueryEditor } from './operation-editor';
99
export { ResponseEditor } from './response-editor';
10-
export { VariableEditor } from './variable-editor';
10+
export { VariablesEditor as VariableEditor } from './variables-editor';
1111

1212
export { Button, UnStyledButton } from './button';
1313
export { ButtonGroup } from './button-group';

packages/graphiql-react/src/components/markdown-content/index.css

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
/**
22
* We render markdown in the following places:
3-
* - In the hint tooltip when typing in the query editor (field description
3+
* - In the hint tooltip when typing in the operation editor (field description
44
* and, optionally, deprecation reason).
5-
* - In the info tooltip when hovering over a field in the query editor
5+
* - In the info tooltip when hovering over a field in the operation editor
66
* (field description and, optionally, deprecation reason).
77
*/
88

@@ -123,8 +123,8 @@
123123

124124
/**
125125
* We show deprecations in the following places:
126-
* - In the hint tooltip when typing in the query editor.
127-
* - In the info tooltip when hovering over a field in the query editor.
126+
* - In the hint tooltip when typing in the operation editor.
127+
* - In the info tooltip when hovering over a field in the operation editor.
128128
*/
129129

130130
.CodeMirror-hint-information-deprecation,

packages/graphiql-react/src/components/query-editor.tsx renamed to packages/graphiql-react/src/components/operation-editor.tsx

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,22 @@ import {
1616
import type { MonacoEditor, EditorProps, SchemaReference } from '../types';
1717
import {
1818
KEY_BINDINGS,
19-
MONACO_GRAPHQL_API,
20-
QUERY_URI,
19+
URI_NAME,
2120
STORAGE_KEY,
21+
MONACO_GRAPHQL_DIAGNOSTIC_SETTINGS,
22+
MONACO_GRAPHQL_API,
2223
} from '../constants';
2324
import {
2425
type editor as monacoEditor,
2526
languages,
2627
Range,
28+
Uri,
2729
} from '../monaco-editor';
2830
import * as monaco from '../monaco-editor';
2931
import { getContextAtPosition } from 'graphql-language-service/esm/parser';
3032
import { toGraphQLPosition } from 'monaco-graphql/esm/utils';
3133

32-
interface QueryEditorProps extends EditorProps {
34+
interface OperationEditorProps extends EditorProps {
3335
/**
3436
* Invoked when a reference to the GraphQL schema (type or field) is clicked
3537
* as part of the editor or one of its tooltips.
@@ -38,14 +40,14 @@ interface QueryEditorProps extends EditorProps {
3840
onClickReference?(reference: SchemaReference): void;
3941

4042
/**
41-
* Invoked when the contents of the query editor change.
43+
* Invoked when the contents of the operation editor change.
4244
* @param value - The new contents of the editor.
4345
* @param documentAST - The editor contents parsed into a GraphQL document.
4446
*/
4547
onEdit?(value: string, documentAST?: DocumentNode): void;
4648
}
4749

48-
export const QueryEditor: FC<QueryEditorProps> = ({
50+
export const OperationEditor: FC<OperationEditorProps> = ({
4951
onClickReference,
5052
onEdit,
5153
...props
@@ -69,6 +71,7 @@ export const QueryEditor: FC<QueryEditorProps> = ({
6971
operations,
7072
operationName,
7173
externalFragments,
74+
uriInstanceId,
7275
} = useGraphiQL(
7376
pick(
7477
'initialQuery',
@@ -77,11 +80,12 @@ export const QueryEditor: FC<QueryEditorProps> = ({
7780
'operations',
7881
'operationName',
7982
'externalFragments',
83+
'uriInstanceId',
8084
),
8185
);
8286
const storage = useStorage();
8387
const ref = useRef<HTMLDivElement>(null!);
84-
const onClickReferenceRef = useRef<QueryEditorProps['onClickReference']>(
88+
const onClickReferenceRef = useRef<OperationEditorProps['onClickReference']>(
8589
null!,
8690
);
8791
useEffect(() => {
@@ -97,7 +101,7 @@ export const QueryEditor: FC<QueryEditorProps> = ({
97101
closeOnUnfocus: false,
98102
completeSingle: false,
99103
autocompleteOptions: {
100-
// for the query editor, restrict to executable type definitions
104+
// for the operation editor, restrict to executable type definitions
101105
mode: GraphQLDocumentMode.EXECUTABLE,
102106
},
103107
},
@@ -206,8 +210,22 @@ export const QueryEditor: FC<QueryEditorProps> = ({
206210
}, [operationName, operations, run, setOperationName]);
207211

208212
useEffect(() => {
213+
const operationUri = Uri.file(`${uriInstanceId}${URI_NAME.operation}`);
214+
const variablesUri = Uri.file(`${uriInstanceId}${URI_NAME.variables}`);
215+
/**
216+
* Mutate the global `validateVariablesJSON` object to setup which operation editor is validated
217+
* by which variables editor. Since we can have multiple GraphiQL instances on the same page.
218+
*/
219+
const { validateVariablesJSON } = MONACO_GRAPHQL_DIAGNOSTIC_SETTINGS;
220+
validateVariablesJSON![operationUri.toString()] = [variablesUri.toString()];
221+
MONACO_GRAPHQL_API.setDiagnosticSettings(
222+
MONACO_GRAPHQL_DIAGNOSTIC_SETTINGS,
223+
);
209224
globalThis.__MONACO = monaco;
210-
const model = getOrCreateModel({ uri: QUERY_URI, value: initialQuery });
225+
const model = getOrCreateModel({
226+
uri: operationUri.path.replace('/', ''),
227+
value: initialQuery,
228+
});
211229
const editor = createEditor(ref, { model });
212230
setEditor({ queryEditor: editor });
213231

@@ -254,7 +272,9 @@ export const QueryEditor: FC<QueryEditorProps> = ({
254272
if (!schema) {
255273
return;
256274
}
257-
MONACO_GRAPHQL_API.setSchemaConfig([{ uri: 'schema.graphql', schema }]);
275+
MONACO_GRAPHQL_API.setSchemaConfig([
276+
{ uri: `${uriInstanceId}${URI_NAME.schema}`, schema },
277+
]);
258278
MONACO_GRAPHQL_API.setExternalFragmentDefinitions([
259279
...externalFragments.values(),
260280
]);
@@ -322,6 +342,7 @@ export const QueryEditor: FC<QueryEditorProps> = ({
322342
setSchemaReference,
323343
setVisiblePlugin,
324344
externalFragments,
345+
uriInstanceId,
325346
]);
326347

327348
return (

packages/graphiql-react/src/components/provider.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* eslint sort-keys: "error" */
22
import type { ComponentPropsWithoutRef, FC, ReactNode, RefObject } from 'react';
3-
import { createContext, useContext, useRef, useEffect } from 'react';
3+
import { createContext, useContext, useRef, useEffect, useId } from 'react';
44
import { create, useStore, UseBoundStore, StoreApi } from 'zustand';
55
import { useShallow } from 'zustand/shallow';
66
import {
@@ -156,7 +156,7 @@ const InnerGraphiQLProvider: FC<InnerGraphiQLProviderProps> = ({
156156
}) => {
157157
const storage = useStorage();
158158
const storeRef = useRef<GraphiQLStore>(null!);
159-
159+
const uriInstanceId = useId();
160160
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- false positive
161161
if (storeRef.current === null) {
162162
function getInitialVisiblePlugin() {
@@ -213,6 +213,11 @@ const InnerGraphiQLProvider: FC<InnerGraphiQLProviderProps> = ({
213213
onTabChange,
214214
shouldPersistHeaders: $shouldPersistHeaders,
215215
tabs,
216+
/**
217+
* Strip colons (React 18) and arrows (React 19) because it breaks monaco-editor
218+
* variables autocomplete
219+
*/
220+
uriInstanceId: uriInstanceId.replaceAll(/[:«»]/g, '') + '-',
216221
})(...args);
217222
const executionSlice = createExecutionSlice({
218223
fetcher,

packages/graphiql-react/src/components/header-editor.tsx renamed to packages/graphiql-react/src/components/request-headers-editor.tsx

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { FC, useEffect, useRef } from 'react';
22
import { useGraphiQL, useGraphiQLActions } from './provider';
33
import type { EditorProps } from '../types';
4-
import { HEADER_URI, KEY_BINDINGS, STORAGE_KEY } from '../constants';
4+
import { URI_NAME, KEY_BINDINGS, STORAGE_KEY } from '../constants';
55
import {
66
getOrCreateModel,
77
createEditor,
@@ -12,18 +12,21 @@ import {
1212
cn,
1313
} from '../utility';
1414

15-
interface HeaderEditorProps extends EditorProps {
15+
interface RequestHeadersEditorProps extends EditorProps {
1616
/**
17-
* Invoked when the contents of the headers editor change.
17+
* Invoked when the contents of the request headers editor change.
1818
* @param value - The new contents of the editor.
1919
*/
2020
onEdit?(value: string): void;
2121
}
2222

23-
export const HeaderEditor: FC<HeaderEditorProps> = ({ onEdit, ...props }) => {
23+
export const RequestHeadersEditor: FC<RequestHeadersEditorProps> = ({
24+
onEdit,
25+
...props
26+
}) => {
2427
const { setEditor, run, prettifyEditors, mergeQuery } = useGraphiQLActions();
25-
const { initialHeaders, shouldPersistHeaders } = useGraphiQL(
26-
pick('initialHeaders', 'shouldPersistHeaders'),
28+
const { initialHeaders, shouldPersistHeaders, uriInstanceId } = useGraphiQL(
29+
pick('initialHeaders', 'shouldPersistHeaders', 'uriInstanceId'),
2730
);
2831
const ref = useRef<HTMLDivElement>(null!);
2932
useChangeHandler(
@@ -32,7 +35,10 @@ export const HeaderEditor: FC<HeaderEditorProps> = ({ onEdit, ...props }) => {
3235
'headers',
3336
);
3437
useEffect(() => {
35-
const model = getOrCreateModel({ uri: HEADER_URI, value: initialHeaders });
38+
const model = getOrCreateModel({
39+
uri: `${uriInstanceId}${URI_NAME.requestHeaders}`,
40+
value: initialHeaders,
41+
});
3642
const editor = createEditor(ref, { model });
3743
setEditor({ headerEditor: editor });
3844
const disposables = [

0 commit comments

Comments
 (0)