Skip to content

graphiql 5: fix GraphiQL 5 feedback #4026

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 26 commits into from
Jun 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .changeset/tender-years-destroy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
'@graphiql/plugin-doc-explorer': minor
'@graphiql/plugin-history': minor
'@graphiql/react': minor
'graphiql': minor
---

- deprecate `useExplorerContext`, `useHistoryContext`, `usePrettifyEditors`, `useCopyQuery`, `useMergeQuery`, `useExecutionContext`, `usePluginContext`, `useSchemaContext`, `useStorageContext` hooks
- fix response editor overflow on `<GraphiQL.Footer />`
- export `GraphiQLProps` type
- allow `children: ReactNode` for `<GraphiQL.Toolbar />`
- change `ToolbarMenu` component:
- The `label` and `className` props were removed
- The `button` prop should now be a button element
- document `useGraphiQL` and `useGraphiQLActions` hooks in `@graphiql/react` README.md
- rename `useThemeStore` to `useTheme`
22 changes: 22 additions & 0 deletions docs/migration/graphiql-5.0.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,25 @@
- `keyMap`. To use Vim or Emacs keybindings in Monaco, you can use community plugins. Monaco Vim: https://github.com/brijeshb42/monaco-vim. Monaco Emacs: https://github.com/aioutecism/monaco-emacs
- `readOnly`
- `validationRules`. Use custom GraphQL worker, see https://github.com/graphql/graphiql/tree/main/packages/monaco-graphql#custom-webworker-for-passing-non-static-config-to-worker.'

## `@graphiql/react`

The `ToolbarMenu` component has changed.

- The `label` and `className` props were removed
- The `button` prop should now be a button element

```jsx
<ToolbarMenu
label="Options"
button={
<ToolbarButton label="Options">
<SettingsIcon className="graphiql-toolbar-icon" aria-hidden="true" />
</ToolbarButton>
}
>
<ToolbarMenu.Item onSelect={() => console.log('Clicked!')}>
Test
</ToolbarMenu.Item>
</ToolbarMenu>
```
13 changes: 13 additions & 0 deletions packages/graphiql-plugin-doc-explorer/src/deprecated.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { useDocExplorer, useDocExplorerActions } from './context';

/**
* @deprecated Use `useDocExplorerActions` and `useDocExplorer` hooks instead.
*/
export function useExplorerContext() {
const actions = useDocExplorerActions();
const explorerNavStack = useDocExplorer();
return {
...actions,
explorerNavStack,
};
}
1 change: 1 addition & 0 deletions packages/graphiql-plugin-doc-explorer/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ export type {
DocExplorerNavStack,
DocExplorerNavStackItem,
} from './context';
export * from './deprecated';
10 changes: 10 additions & 0 deletions packages/graphiql-plugin-history/src/deprecated.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { useHistory, useHistoryActions } from './context';

/**
* @deprecated Use `useHistoryActions` and `useHistory` hooks instead.
*/
export function useHistoryContext() {
const actions = useHistoryActions();
const items = useHistory();
return { ...actions, items };
}
1 change: 1 addition & 0 deletions packages/graphiql-plugin-history/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ export const HISTORY_PLUGIN: GraphiQLPlugin = {
export { History };

export { HistoryStore, useHistory, useHistoryActions } from './context';
export * from './deprecated';
53 changes: 38 additions & 15 deletions packages/graphiql-react/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,31 +74,54 @@ text. If you want to use the default fonts you can load them using these files:
- `@graphiql/react/font/roboto.css`
- `@graphiql/react/font/fira-code.css`.

You can of course use any other method to load these fonts (for example loading
You can, of course, use any other method to load these fonts (for example, loading
them from Google Fonts).

Further details on how to use `@graphiql/react` can be found in the reference
implementation of a GraphQL IDE - Graph*i*QL - in the
[`graphiql` package](https://github.com/graphql/graphiql/blob/main/packages/graphiql/src/components/GraphiQL.tsx).

## Available stores
## Available Stores

There are multiple stores that own different parts of the state that make up a
complete GraphQL IDE. For each store there is a component
(`<name>Store`) that makes sure the store is initialized and managed
properly. These components contains all the logic related to state management.
GraphiQL uses a set of state management stores, each responsible for a specific part of the IDE's
behavior. These stores contain all logic related to state management and can be accessed via custom
React hooks.

In addition, for each store, there is also a hook that
allows you to consume its current value:
### Core Hooks

- `useStorage`: Provides a storage API that can be used to persist state in
the browser (by default using `localStorage`)
- `useEditorStore`: Manages all the editors and tabs
- `useSchemaStore`: Fetches, validates and stores the GraphQL schema
- `useExecutionStore`: Executes GraphQL requests
- **`useStorage`**: Provides a storage API that can be used to persist state in the browser (by default using `localStorage`).
- **`useTheme`**: Manages the current theme and provides a method to update it.
- **`useGraphiQL`**: Access the current state.
- **`useGraphiQLActions`**: Trigger actions that mutate the state. This hook **never** rerenders.

All context properties are documented using JSDoc comments. If you're using an
IDE like VSCode for development these descriptions will show up in auto-complete
The `useGraphiQLActions` hook **exposes all actions** across store slices.
The `useGraphiQL` hook **provides access to the following store slices**:

| Store Slice | Responsibilities |
| ----------- | -------------------------------------------------------------------------------- |
| `editor` | Manages **query**, **variables**, **headers**, and **response** editors and tabs |
| `execution` | Handles the execution of GraphQL requests |
| `plugin` | Manages plugins and the currently active plugin |
| `schema` | Fetches, validates, and stores the GraphQL schema |

### Usage Example

```js
import { useGraphiQL, useGraphiQLActions, useTheme } from '@graphiql/react';

// Get an action to fetch the schema
const { introspect } = useGraphiQLActions();

// Get the current theme and a method to change it
const { theme, setTheme } = useTheme();

// Or use a selector to access specific parts of the state
const schema = useGraphiQL(state => state.schema);
const currentTheme = useTheme(state => state.theme);
```

All store properties are documented using TSDoc comments. If you're using an
IDE like VSCode for development, these descriptions will show up in auto-complete
tooltips. All these descriptions can also be found in the
[API Docs](https://graphiql-test.netlify.app/typedoc/modules/graphiql_react.html).

Expand Down
5 changes: 0 additions & 5 deletions packages/graphiql-react/src/components/toolbar-menu/index.css

This file was deleted.

31 changes: 8 additions & 23 deletions packages/graphiql-react/src/components/toolbar-menu/index.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,19 @@
import type { FC, ReactNode } from 'react';
import { cn } from '../../utility';
import type { DropdownMenuProps } from '@radix-ui/react-dropdown-menu';
import { Trigger, type DropdownMenuProps } from '@radix-ui/react-dropdown-menu';
import { DropdownMenu } from '../dropdown-menu';
import { Tooltip } from '../tooltip';
import './index.css';

interface ToolbarMenuProps {
interface ToolbarMenuProps extends DropdownMenuProps {
button: ReactNode;
label: string;
}

const ToolbarMenuRoot: FC<
ToolbarMenuProps & {
children: ReactNode;
className?: string;
} & DropdownMenuProps
> = ({ button, children, label, ...props }) => {
const ToolbarMenuRoot: FC<ToolbarMenuProps> = ({
button,
children,
...props
}) => {
return (
<DropdownMenu {...props}>
<Tooltip label={label}>
<DropdownMenu.Button
className={cn(
'graphiql-un-styled graphiql-toolbar-menu',
props.className,
)}
aria-label={label}
>
{button}
</DropdownMenu.Button>
</Tooltip>
<Trigger asChild>{button}</Trigger>
<DropdownMenu.Content>{children}</DropdownMenu.Content>
</DropdownMenu>
);
Expand Down
76 changes: 76 additions & 0 deletions packages/graphiql-react/src/deprecated.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/* eslint-disable unicorn/prefer-export-from */
import { useGraphiQL, useGraphiQLActions } from './components';
import { pick } from './utility';
import { useStorage } from './stores';

/**
* @deprecated Use `const { prettifyEditors } = useGraphiQLActions()` instead.
*/
export function usePrettifyEditors() {
const { prettifyEditors } = useGraphiQLActions();
return prettifyEditors;
}

/**
* @deprecated Use `const { copyQuery } = useGraphiQLActions()` instead.
*/
export function useCopyQuery() {
const { copyQuery } = useGraphiQLActions();
return copyQuery;
}

/**
* @deprecated Use `const { mergeQuery } = useGraphiQLActions()` instead.
*/
export function useMergeQuery() {
const { mergeQuery } = useGraphiQLActions();
return mergeQuery;
}

/**
* @deprecated Use `useGraphiQLActions` and `useGraphiQL` hooks instead.
*/
export function useExecutionContext() {
const { run, stop } = useGraphiQLActions();
const values = useGraphiQL(state => ({
isFetching: state.isIntrospecting,
isSubscribed: Boolean(state.subscription),
operationName: state.operationName,
}));
return {
run,
stop,
...values,
};
}

/**
* @deprecated Use `useGraphiQLActions` and `useGraphiQL` hooks instead.
*/
export function usePluginContext() {
const { setVisiblePlugin } = useGraphiQLActions();
const values = useGraphiQL(pick('plugins', 'visiblePlugin'));
return {
setVisiblePlugin,
...values,
};
}

/**
* @deprecated Use `useGraphiQLActions` and `useGraphiQL` hooks instead.
*/
export function useSchemaContext() {
const { introspect } = useGraphiQLActions();
const values = useGraphiQL(
pick('fetchError', 'isFetching', 'schema', 'validationErrors'),
);
return {
introspect,
...values,
};
}

/**
* @deprecated Use `const storage = useStorage()` instead.
*/
export const useStorageContext = useStorage;
3 changes: 2 additions & 1 deletion packages/graphiql-react/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import './style/root.css';

export { useStorage, useThemeStore, type Theme } from './stores';
export { useStorage, useTheme, type Theme } from './stores';

export * from './utility';
export type { TabsState } from './utility/tabs';
Expand All @@ -10,3 +10,4 @@ export * from './components';
export type { EditorProps, SchemaReference, SlicesWithActions } from './types';
export type { GraphiQLPlugin } from './stores/plugin';
export { KEY_MAP, formatShortcutForOS, isMacOs } from './constants';
export * from './deprecated';
2 changes: 1 addition & 1 deletion packages/graphiql-react/src/stores/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@ export {
type SchemaProps,
} from './schema';
export { storageStore, useStorage } from './storage';
export { themeStore, useThemeStore, type Theme } from './theme';
export { themeStore, useTheme, type Theme } from './theme';
4 changes: 2 additions & 2 deletions packages/graphiql-react/src/stores/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const storageStore = createStore<StorageStoreType>(() => ({
}));

export const StorageStore: FC<StorageStoreProps> = ({ storage, children }) => {
const isMounted = useStorageStore(store => Boolean(store.storage));
const isMounted = useStorageStore(state => Boolean(state.storage));

useEffect(() => {
storageStore.setState({ storage: new StorageAPI(storage) });
Expand All @@ -40,4 +40,4 @@ export const StorageStore: FC<StorageStoreProps> = ({ storage, children }) => {

const useStorageStore = createBoundedUseStore(storageStore);

export const useStorage = () => useStorageStore(store => store.storage);
export const useStorage = () => useStorageStore(state => state.storage);
4 changes: 2 additions & 2 deletions packages/graphiql-react/src/stores/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export const ThemeStore: FC<ThemeStoreProps> = ({
defaultTheme = null,
editorTheme = EDITOR_THEME,
}) => {
const theme = useThemeStore(store => store.theme);
const theme = useTheme(state => state.theme);
useEffect(() => {
const { storage } = storageStore.getState();

Expand Down Expand Up @@ -104,4 +104,4 @@ function getSystemTheme() {
return systemTheme;
}

export const useThemeStore = createBoundedUseStore(themeStore);
export const useTheme = createBoundedUseStore(themeStore);
7 changes: 0 additions & 7 deletions packages/graphiql-react/src/style/codemirror.css
Original file line number Diff line number Diff line change
@@ -1,10 +1,3 @@
/* Set default background color */
.graphiql-container .CodeMirror,
.graphiql-container .CodeMirror-gutters {
background: none;
background-color: var(--editor-background, hsl(var(--color-base)));
}

/* No padding around line numbers */
.graphiql-container .CodeMirror-linenumber {
padding: 0;
Expand Down
5 changes: 0 additions & 5 deletions packages/graphiql-react/src/style/editor.css
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,6 @@
}
}

.graphiql-response .monaco-editor {
/* Add some padding so it doesn’t touch the tabs */
padding-top: var(--px-16);
}

/* Make hover contents be dynamic */
.monaco-hover,
.monaco-hover-content {
Expand Down
5 changes: 4 additions & 1 deletion packages/graphiql/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,14 @@
"files": [
"dist",
"!dist/e2e.*",
"!dist/workers/*"
"!dist/workers/*",
"!dist/index.umd.*",
"!dist/cdn.*"
],
"exports": {
"./package.json": "./package.json",
"./style.css": "./dist/style.css",
"./graphiql.css": "./dist/style.css",
".": "./dist/index.js",
"./setup-workers/*": {
"types": "./dist/setup-workers/*.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion packages/graphiql/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@
*/
import './style.css';

export { GraphiQL } from './GraphiQL';
export { GraphiQL, type GraphiQLProps } from './GraphiQL';

export { HISTORY_PLUGIN } from '@graphiql/plugin-history';
3 changes: 2 additions & 1 deletion packages/graphiql/src/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,8 @@ button.graphiql-tab-add {

/* The response view */
.graphiql-container .graphiql-response {
--editor-background: transparent;
/* Add some padding so it doesn’t touch the tabs */
padding-top: var(--px-16);
display: flex;
width: 100%;
flex-direction: column;
Expand Down
Loading
Loading