Skip to content

Commit f15ee38

Browse files
fix initial state computation to avoid duplicate tabs (#2708)
* fix initial state computation to avoid duplicate tabs * fix `defaultQuery` prop
1 parent eb61a4b commit f15ee38

3 files changed

Lines changed: 63 additions & 32 deletions

File tree

.changeset/healthy-cougars-sell.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@graphiql/react': patch
3+
---
4+
5+
Fix computing the initial state for editor values and tabs to avoid duplicating tabs on page reload

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

Lines changed: 42 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@ import {
77
visit,
88
} from 'graphql';
99
import { VariableToType } from 'graphql-language-service';
10-
import { ReactNode, useCallback, useMemo, useRef, useState } from 'react';
10+
import { ReactNode, useCallback, useMemo, useState } from 'react';
1111

1212
import { useStorageContext } from '../storage';
1313
import { createContextHook, createNullableContext } from '../utility/context';
1414
import { STORAGE_KEY as STORAGE_KEY_HEADERS } from './header-editor';
1515
import { useSynchronizeValue } from './hooks';
1616
import { STORAGE_KEY_QUERY } from './query-editor';
1717
import {
18-
emptyTab,
18+
createTab,
1919
getDefaultTabState,
2020
setPropertiesInActiveTab,
2121
TabsState,
@@ -239,22 +239,43 @@ export function EditorContextProvider(props: EditorContextProviderProps) {
239239
useSynchronizeValue(responseEditor, props.response);
240240
useSynchronizeValue(variableEditor, props.variables);
241241

242-
// We store this in state but never update it. By passing a function we only
243-
// need to compute it lazily during the initial render.
244-
const [storedEditorValues] = useState(() => ({
245-
headers: props.headers ?? storage?.get(STORAGE_KEY_HEADERS) ?? null,
246-
query: props.query ?? storage?.get(STORAGE_KEY_QUERY) ?? null,
247-
variables: props.variables ?? storage?.get(STORAGE_KEY_VARIABLES) ?? null,
248-
}));
249-
250-
const [tabState, setTabState] = useState<TabsState>(() =>
251-
getDefaultTabState({ ...storedEditorValues, storage }),
252-
);
253-
254242
const storeTabs = useStoreTabs({
255243
storage,
256244
shouldPersistHeaders: props.shouldPersistHeaders,
257245
});
246+
247+
// We store this in state but never update it. By passing a function we only
248+
// need to compute it lazily during the initial render.
249+
const [initialState] = useState(() => {
250+
const query = props.query ?? storage?.get(STORAGE_KEY_QUERY) ?? null;
251+
const variables =
252+
props.variables ?? storage?.get(STORAGE_KEY_VARIABLES) ?? null;
253+
const headers = props.headers ?? storage?.get(STORAGE_KEY_HEADERS) ?? null;
254+
const response = props.response ?? '';
255+
256+
const tabState = getDefaultTabState({
257+
query,
258+
variables,
259+
headers,
260+
defaultQuery: props.defaultQuery || DEFAULT_QUERY,
261+
storage,
262+
});
263+
storeTabs(tabState);
264+
265+
return {
266+
query:
267+
query ??
268+
(tabState.activeTabIndex === 0 ? tabState.tabs[0].query : null) ??
269+
'',
270+
variables: variables ?? '',
271+
headers: headers ?? '',
272+
response,
273+
tabState,
274+
};
275+
});
276+
277+
const [tabState, setTabState] = useState<TabsState>(initialState.tabState);
278+
258279
const synchronizeActiveTabValues = useSynchronizeActiveTabValues({
259280
queryEditor,
260281
variableEditor,
@@ -274,7 +295,7 @@ export function EditorContextProvider(props: EditorContextProviderProps) {
274295
// Make sure the current tab stores the latest values
275296
const updatedValues = synchronizeActiveTabValues(current);
276297
const updated = {
277-
tabs: [...updatedValues.tabs, emptyTab()],
298+
tabs: [...updatedValues.tabs, createTab()],
278299
activeTabIndex: updatedValues.tabs.length,
279300
};
280301
storeTabs(updated);
@@ -344,15 +365,6 @@ export function EditorContextProvider(props: EditorContextProviderProps) {
344365
[onEditOperationName, queryEditor, updateActiveTabValues],
345366
);
346367

347-
const defaultQuery =
348-
tabState.activeTabIndex > 0 ? '' : props.defaultQuery ?? DEFAULT_QUERY;
349-
const initialValues = useRef({
350-
initialHeaders: storedEditorValues.headers ?? '',
351-
initialQuery: storedEditorValues.query ?? defaultQuery,
352-
initialResponse: props.response ?? '',
353-
initialVariables: storedEditorValues.variables ?? '',
354-
});
355-
356368
const externalFragments = useMemo(() => {
357369
const map = new Map<string, FragmentDefinitionNode>();
358370
if (Array.isArray(props.externalFragments)) {
@@ -397,7 +409,10 @@ export function EditorContextProvider(props: EditorContextProviderProps) {
397409

398410
setOperationName,
399411

400-
...initialValues.current,
412+
initialQuery: initialState.query,
413+
initialVariables: initialState.variables,
414+
initialHeaders: initialState.headers,
415+
initialResponse: initialState.response,
401416

402417
externalFragments,
403418
validationRules,
@@ -418,6 +433,8 @@ export function EditorContextProvider(props: EditorContextProviderProps) {
418433

419434
setOperationName,
420435

436+
initialState,
437+
421438
externalFragments,
422439
validationRules,
423440

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

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,13 @@ export type TabsState = {
6262
};
6363

6464
export function getDefaultTabState({
65+
defaultQuery,
6566
headers,
6667
query,
6768
variables,
6869
storage,
6970
}: {
71+
defaultQuery: string;
7072
headers: string | null;
7173
query: string | null;
7274
variables: string | null;
@@ -108,15 +110,18 @@ export function getDefaultTabState({
108110
operationName,
109111
response: null,
110112
});
113+
parsed.activeTabIndex = parsed.tabs.length - 1;
111114
}
112115

113116
return parsed;
114117
} else {
115118
throw new Error('Storage for tabs is invalid');
116119
}
117120
} catch (err) {
118-
storage?.set(STORAGE_KEY, '');
119-
return { activeTabIndex: 0, tabs: [emptyTab()] };
121+
return {
122+
activeTabIndex: 0,
123+
tabs: [createTab({ query: query ?? defaultQuery, variables, headers })],
124+
};
120125
}
121126
}
122127

@@ -252,14 +257,18 @@ export function useSetEditorValues({
252257
);
253258
}
254259

255-
export function emptyTab(): TabState {
260+
export function createTab({
261+
query = null,
262+
variables = null,
263+
headers = null,
264+
}: Partial<Pick<TabState, 'query' | 'variables' | 'headers'>> = {}): TabState {
256265
return {
257266
id: guid(),
258-
hash: hashFromTabContents({ query: null, variables: null, headers: null }),
267+
hash: hashFromTabContents({ query, variables, headers }),
259268
title: DEFAULT_TITLE,
260-
query: null,
261-
variables: null,
262-
headers: null,
269+
query,
270+
variables,
271+
headers,
263272
operationName: null,
264273
response: null,
265274
};

0 commit comments

Comments
 (0)