Skip to content

Commit 4936492

Browse files
authored
graphiql 5: separate store actions from state, add useGraphiQLActions state (#4009)
* add f1 command as first item in shortcut table * add f1 command as first item in shortcut table * rollback * refactor plugins * refactor editor * refactor execution * refactor schema * refactor schema * export actions * add SlicesWithActions * add types tests * execution * lint * editor refactoring * avoid calling get() in set() * fix state creators * useGraphiQLActions * add comment * fix type errors * fix type errors * more types check * more types check * more types check * upd * add changeset * Update editor.ts
1 parent 8adfaaf commit 4936492

File tree

25 files changed

+801
-703
lines changed

25 files changed

+801
-703
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
'@graphiql/plugin-doc-explorer': minor
3+
'@graphiql/plugin-explorer': major
4+
'@graphiql/react': minor
5+
'graphiql': major
6+
---
7+
8+
separate store actions from state, add `useGraphiQLActions` state

.eslintrc.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ module.exports = {
183183
'init-declarations': 'off',
184184
'no-catch-shadow': 'error',
185185
'no-label-var': 'error',
186-
'no-restricted-globals': 'off',
186+
'no-restricted-globals': ['error', 'stop'],
187187
'no-shadow': 'off',
188188
'@typescript-eslint/no-shadow': 'error',
189189
'no-undef-init': 'off',

packages/graphiql-plugin-doc-explorer/src/components/__tests__/type-documentation.spec.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@ import {
1010
import { docExplorerStore } from '../../context';
1111
import { TypeDocumentation } from '../type-documentation';
1212
import { unwrapType } from './test-utils';
13-
import { AllSlices } from '@graphiql/react';
13+
import { SlicesWithActions } from '@graphiql/react';
1414

1515
vi.mock('@graphiql/react', async () => {
1616
const originalModule =
1717
await vi.importActual<typeof import('@graphiql/react')>('@graphiql/react');
1818
const useGraphiQL: (typeof originalModule)['useGraphiQL'] = cb =>
19-
cb({ schema: ExampleSchema } as AllSlices);
19+
cb({ schema: ExampleSchema } as SlicesWithActions);
2020

2121
return {
2222
...originalModule,

packages/graphiql-plugin-doc-explorer/src/context.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,5 +332,10 @@ const useDocExplorerStore = createBoundedUseStore(docExplorerStore);
332332

333333
export const useDocExplorer = () =>
334334
useDocExplorerStore(state => state.explorerNavStack);
335+
336+
/**
337+
* Actions are functions used to update values in your store. They are static and never change.
338+
* @see https://tkdodo.eu/blog/working-with-zustand#separate-actions-from-state
339+
*/
335340
export const useDocExplorerActions = () =>
336341
useDocExplorerStore(state => state.actions);

packages/graphiql-plugin-explorer/src/index.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { CSSProperties, FC, useCallback } from 'react';
22
import {
33
GraphiQLPlugin,
44
useGraphiQL,
5-
pick,
5+
useGraphiQLActions,
66
useOperationsEditorState,
77
useOptimisticState,
88
} from '@graphiql/react';
@@ -61,9 +61,8 @@ export type GraphiQLExplorerPluginProps = Omit<
6161
>;
6262

6363
const ExplorerPlugin: FC<GraphiQLExplorerPluginProps> = props => {
64-
const { setOperationName, schema, run } = useGraphiQL(
65-
pick('setOperationName', 'schema', 'run'),
66-
);
64+
const { setOperationName, run } = useGraphiQLActions();
65+
const schema = useGraphiQL(state => state.schema);
6766

6867
// handle running the current operation from the plugin
6968
const handleRunOperation = useCallback(

packages/graphiql-plugin-history/src/context.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,4 +155,9 @@ const useHistoryStore = createBoundedUseStore(historyStore);
155155

156156
export const useHistory = () =>
157157
useHistoryStore(state => state.historyStorage.queries);
158+
159+
/**
160+
* Actions are functions used to update values in your store. They are static and never change.
161+
* @see https://tkdodo.eu/blog/working-with-zustand#separate-actions-from-state
162+
*/
158163
export const useHistoryActions = () => useHistoryStore(state => state.actions);

packages/graphiql-react/src/components/execute-button/index.tsx

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { FC } from 'react';
2-
import { useGraphiQL } from '../provider';
1+
import type { FC } from 'react';
2+
import { useGraphiQL, useGraphiQLActions } from '../provider';
33
import { PlayIcon, StopIcon } from '../../icons';
44
import { DropdownMenu } from '../dropdown-menu';
55
import { Tooltip } from '../tooltip';
@@ -8,24 +8,14 @@ import { pick } from '../../utility';
88
import './index.css';
99

1010
export const ExecuteButton: FC = () => {
11+
const { setOperationName, run, stop } = useGraphiQLActions();
1112
const {
12-
setOperationName,
1313
operations = [],
1414
operationName,
1515
isFetching,
1616
overrideOperationName,
17-
run,
18-
stop,
1917
} = useGraphiQL(
20-
pick(
21-
'setOperationName',
22-
'operations',
23-
'operationName',
24-
'isFetching',
25-
'overrideOperationName',
26-
'run',
27-
'stop',
28-
),
18+
pick('operations', 'operationName', 'isFetching', 'overrideOperationName'),
2919
);
3020
const isSubscribed = useGraphiQL(state => Boolean(state.subscription));
3121
const hasOptions =

packages/graphiql-react/src/components/header-editor.tsx

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { FC, useEffect, useRef } from 'react';
2-
import { useGraphiQL } from './provider';
2+
import { useGraphiQL, useGraphiQLActions } from './provider';
33
import { EditorProps } from '../types';
44
import { HEADER_URI, KEY_BINDINGS } from '../constants';
55
import {
@@ -21,22 +21,9 @@ interface HeaderEditorProps extends EditorProps {
2121
}
2222

2323
export const HeaderEditor: FC<HeaderEditorProps> = ({ onEdit, ...props }) => {
24-
const {
25-
initialHeaders,
26-
shouldPersistHeaders,
27-
setEditor,
28-
run,
29-
prettifyEditors,
30-
mergeQuery,
31-
} = useGraphiQL(
32-
pick(
33-
'initialHeaders',
34-
'shouldPersistHeaders',
35-
'setEditor',
36-
'run',
37-
'prettifyEditors',
38-
'mergeQuery',
39-
),
24+
const { setEditor, run, prettifyEditors, mergeQuery } = useGraphiQLActions();
25+
const { initialHeaders, shouldPersistHeaders } = useGraphiQL(
26+
pick('initialHeaders', 'shouldPersistHeaders'),
4027
);
4128
const ref = useRef<HTMLDivElement>(null!);
4229
useChangeHandler(

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ export { ToolbarMenu } from './toolbar-menu';
44

55
export { HeaderEditor } from './header-editor';
66
export { ImagePreview } from './image-preview';
7-
export { GraphiQLProvider, useGraphiQL } from './provider';
7+
export { GraphiQLProvider, useGraphiQL, useGraphiQLActions } from './provider';
88
export { QueryEditor } from './query-editor';
99
export { ResponseEditor } from './response-editor';
1010
export { VariableEditor } from './variable-editor';

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

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { PluginProps, createPluginSlice } from '../stores/plugin';
1919
import { SchemaProps, createSchemaSlice } from '../stores/schema';
2020
import { StorageStore, useStorage } from '../stores/storage';
2121
import { ThemeStore } from '../stores/theme';
22-
import { AllSlices } from '../types';
22+
import { SlicesWithActions } from '../types';
2323
import { pick, useSynchronizeValue } from '../utility';
2424
import {
2525
FragmentDefinitionNode,
@@ -48,7 +48,7 @@ type GraphiQLProviderProps =
4848
ComponentPropsWithoutRef<typeof StorageStore> &
4949
ComponentPropsWithoutRef<typeof ThemeStore>;
5050

51-
type GraphiQLStore = UseBoundStore<StoreApi<AllSlices>>;
51+
type GraphiQLStore = UseBoundStore<StoreApi<SlicesWithActions>>;
5252

5353
const GraphiQLContext = createContext<RefObject<GraphiQLStore> | null>(null);
5454

@@ -164,8 +164,8 @@ const InnerGraphiQLProvider: FC<InnerGraphiQLProviderProps> = ({
164164
return map;
165165
})();
166166

167-
const store = create<AllSlices>((...args) => ({
168-
...createEditorSlice({
167+
const store = create<SlicesWithActions>((...args) => {
168+
const editorSlice = createEditorSlice({
169169
activeTabIndex,
170170
defaultHeaders,
171171
defaultQuery,
@@ -181,12 +181,25 @@ const InnerGraphiQLProvider: FC<InnerGraphiQLProviderProps> = ({
181181
onTabChange,
182182
shouldPersistHeaders: $shouldPersistHeaders,
183183
tabs,
184-
})(...args),
185-
...createExecutionSlice(...args),
186-
...createPluginSlice(...args),
187-
...createSchemaSlice(...args),
188-
}));
189-
store.getState().storeTabs({ activeTabIndex, tabs });
184+
})(...args);
185+
const executionSlice = createExecutionSlice(...args);
186+
const pluginSlice = createPluginSlice(...args);
187+
const schemaSlice = createSchemaSlice(...args);
188+
return {
189+
...editorSlice,
190+
...executionSlice,
191+
...pluginSlice,
192+
...schemaSlice,
193+
actions: {
194+
...editorSlice.actions,
195+
...executionSlice.actions,
196+
...pluginSlice.actions,
197+
...schemaSlice.actions,
198+
},
199+
};
200+
});
201+
const { actions } = store.getState();
202+
actions.storeTabs({ activeTabIndex, tabs });
190203
return store;
191204
}
192205

@@ -224,7 +237,7 @@ const InnerGraphiQLProvider: FC<InnerGraphiQLProviderProps> = ({
224237
// storage.set(STORAGE_KEY, '');
225238
// }
226239
const store = storeRef.current;
227-
const { setPlugins, setVisiblePlugin } = store.getState();
240+
const { setPlugins, setVisiblePlugin } = store.getState().actions;
228241

229242
setPlugins(plugins);
230243
setVisiblePlugin(visiblePlugin ?? null);
@@ -260,7 +273,8 @@ const InnerGraphiQLProvider: FC<InnerGraphiQLProviderProps> = ({
260273
}));
261274

262275
// Trigger introspection
263-
void store.getState().introspect();
276+
const { actions } = store.getState();
277+
void actions.introspect();
264278
}, [
265279
schema,
266280
dangerouslyAssumeSchemaIsValid,
@@ -277,7 +291,8 @@ const InnerGraphiQLProvider: FC<InnerGraphiQLProviderProps> = ({
277291
useEffect(() => {
278292
function triggerIntrospection(event: KeyboardEvent) {
279293
if (event.ctrlKey && event.key === 'R') {
280-
void storeRef.current.getState().introspect();
294+
const { actions } = storeRef.current.getState();
295+
void actions.introspect();
281296
}
282297
}
283298

@@ -315,10 +330,16 @@ const SynchronizeValue: FC<SynchronizeValueProps> = ({
315330
return children as ReactElement;
316331
};
317332

318-
export function useGraphiQL<T>(selector: (state: AllSlices) => T): T {
333+
export function useGraphiQL<T>(selector: (state: SlicesWithActions) => T): T {
319334
const store = useContext(GraphiQLContext);
320335
if (!store) {
321336
throw new Error('Missing `GraphiQLContext.Provider` in the tree');
322337
}
323338
return useStore(store.current, useShallow(selector));
324339
}
340+
341+
/**
342+
* Actions are functions used to update values in your store. They are static and never change.
343+
* @see https://tkdodo.eu/blog/working-with-zustand#separate-actions-from-state
344+
*/
345+
export const useGraphiQLActions = () => useGraphiQL(state => state.actions);

0 commit comments

Comments
 (0)