Skip to content

Commit 7275472

Browse files
authored
feat(@graphiql/react): migrate React context to zustand, replace useSchemaContext with useSchemaStore hook (#3943)
* upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * changeset * upd * upd * fix tests * fix example * fix tests * Update packages/graphiql-react/src/schema.ts * Update .changeset/old-beds-juggle.md * prettier
1 parent 00c8605 commit 7275472

File tree

21 files changed

+642
-525
lines changed

21 files changed

+642
-525
lines changed

.changeset/old-beds-juggle.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
'@graphiql/plugin-doc-explorer': patch
3+
'@graphiql/plugin-explorer': patch
4+
'@graphiql/react': minor
5+
'graphiql': patch
6+
---
7+
8+
feat(@graphiql/react): migrate React context to zustand, replace `useSchemaContext` with `useSchemaStore` hook

examples/graphiql-webpack/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"regenerator-runtime": "^0.13.9"
2121
},
2222
"devDependencies": {
23+
"react-hot-loader": "^4.13.1",
2324
"@babel/plugin-proposal-class-properties": "^7.18.6",
2425
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
2526
"@babel/preset-env": "^7.20.2",
@@ -32,7 +33,7 @@
3233
"react-dom": "^19.1.0",
3334
"style-loader": "^3.3.1",
3435
"webpack": "5.94.0",
35-
"webpack-cli": "^5.0.1",
36+
"webpack-cli": "^6.0.1",
3637
"webpack-dev-server": "^4.11.1",
3738
"worker-loader": "^2.0.0",
3839
"workbox-webpack-plugin": "^7.0.0",

examples/graphiql-webpack/src/index.jsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,15 +54,15 @@ if ('serviceWorker' in navigator) {
5454
const style = { height: '100vh' };
5555
/**
5656
* instantiate outside of the component lifecycle
57-
* unless you need to pass it dynamic values from your react app,
57+
* unless you need to pass it dynamic values from your React app,
5858
* then use the `useMemo` hook
5959
*/
6060
const explorer = explorerPlugin();
6161

6262
const App = () => {
63-
const storage = useStorage({ nonNull: true });
64-
65-
const lastUrl = storage.get(LAST_URL_KEY);
63+
// TODO: `storage` will be always `null`, fix it to have access outside `StorageContextProvider` after zustand migration
64+
const storage = useStorage();
65+
const lastUrl = storage?.get(LAST_URL_KEY);
6666
const [currentUrl, setUrl] = React.useState(lastUrl ?? STARTING_URL);
6767
// TODO: a breaking change where we make URL an internal state concern, and then expose hooks
6868
// so that you can handle/set URL state internally from a plugin

examples/graphiql-webpack/src/select-server-plugin.jsx

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as React from 'react';
22

33
import './select-server-plugin.css';
4-
import { useStorage, useSchemaContext } from '@graphiql/react';
4+
import { useStorage, useSchemaStore } from '@graphiql/react';
55

66
export const LAST_URL_KEY = 'lastURL';
77

@@ -18,10 +18,9 @@ const SelectServer = ({ url, setUrl }) => {
1818
);
1919
const [error, setError] = React.useState(null);
2020

21-
const schema = useSchemaContext();
21+
const { schema, isFetching, fetchError } = useSchemaStore();
2222

2323
const sameValue = inputValue.trim() === url;
24-
console.log(schema);
2524

2625
return (
2726
<div>
@@ -62,27 +61,27 @@ const SelectServer = ({ url, setUrl }) => {
6261
>
6362
Change Schema URL
6463
</button>
65-
{schema?.fetchError && (
64+
{fetchError && (
6665
<div>
6766
<div className="select-server--schema-error">
6867
There was an error fetching your schema:
6968
</div>
7069
<div className="select-server--schema-error">
7170
<code>
72-
{JSON.parse(schema.fetchError).errors.map(
73-
({ message }) => message,
74-
)}
71+
{JSON.parse(fetchError).errors.map(({ message }) => message)}
7572
</code>
7673
</div>
7774
</div>
7875
)}
79-
{schema?.schema && !schema?.isFetching && !schema?.fetchError && (
80-
<div className="select-server--schema-success">
81-
Schema retrieved successfully
82-
</div>
83-
)}
84-
{schema?.isFetching && (
76+
{isFetching ? (
8577
<div className="select-server--schema-loading">Schema loading...</div>
78+
) : (
79+
schema &&
80+
!fetchError && (
81+
<div className="select-server--schema-success">
82+
Schema retrieved successfully
83+
</div>
84+
)
8685
)}
8786
</div>
8887
<div>

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

Lines changed: 66 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1-
import { render } from '@testing-library/react';
1+
import { act, render } from '@testing-library/react';
22
import { GraphQLInt, GraphQLObjectType, GraphQLSchema } from 'graphql';
33
import { FC, useEffect } from 'react';
4-
import { SchemaContext, SchemaContextType } from '@graphiql/react';
4+
import type { SchemaContextType } from '@graphiql/react';
55
import {
66
DocExplorerContextProvider,
77
useDocExplorer,
88
useDocExplorerActions,
99
} from '../../context';
1010
import { DocExplorer } from '../doc-explorer';
11+
import { schemaStore } from '../../../../graphiql-react/dist/schema';
1112

1213
function makeSchema(fieldName = 'field') {
1314
return new GraphQLSchema({
@@ -34,8 +35,6 @@ const defaultSchemaContext: SchemaContextType = {
3435
isFetching: false,
3536
schema: makeSchema(),
3637
validationErrors: [],
37-
schemaReference: null!,
38-
setSchemaReference: null!,
3938
};
4039

4140
const withErrorSchemaContext: SchemaContextType = {
@@ -58,59 +57,37 @@ const DocExplorerWithContext: FC = () => {
5857

5958
describe('DocExplorer', () => {
6059
it('renders spinner when the schema is loading', () => {
61-
const { container } = render(
62-
<SchemaContext.Provider
63-
value={{
64-
...defaultSchemaContext,
65-
isFetching: true,
66-
schema: undefined,
67-
}}
68-
>
69-
<DocExplorerWithContext />
70-
</SchemaContext.Provider>,
71-
);
60+
schemaStore.setState({ isFetching: true });
61+
const { container } = render(<DocExplorerWithContext />);
7262
const spinner = container.querySelectorAll('.graphiql-spinner');
7363
expect(spinner).toHaveLength(1);
7464
});
7565
it('renders with null schema', () => {
76-
const { container } = render(
77-
<SchemaContext.Provider value={{ ...defaultSchemaContext, schema: null }}>
78-
<DocExplorerWithContext />
79-
</SchemaContext.Provider>,
80-
);
66+
schemaStore.setState({ ...defaultSchemaContext, schema: null });
67+
const { container } = render(<DocExplorerWithContext />);
8168
const error = container.querySelectorAll('.graphiql-doc-explorer-error');
8269
expect(error).toHaveLength(1);
8370
expect(error[0]).toHaveTextContent('No GraphQL schema available');
8471
});
8572
it('renders with schema', () => {
86-
const { container } = render(
87-
<SchemaContext.Provider value={defaultSchemaContext}>
88-
<DocExplorerWithContext />,
89-
</SchemaContext.Provider>,
90-
);
73+
schemaStore.setState(defaultSchemaContext);
74+
const { container } = render(<DocExplorerWithContext />);
9175
const error = container.querySelectorAll('.graphiql-doc-explorer-error');
9276
expect(error).toHaveLength(0);
9377
expect(
9478
container.querySelector('.graphiql-markdown-description'),
9579
).toHaveTextContent('GraphQL Schema for testing');
9680
});
9781
it('renders correctly with schema error', () => {
98-
const { rerender, container } = render(
99-
<SchemaContext.Provider value={withErrorSchemaContext}>
100-
<DocExplorerWithContext />,
101-
</SchemaContext.Provider>,
102-
);
103-
82+
schemaStore.setState(withErrorSchemaContext);
83+
const { rerender, container } = render(<DocExplorerWithContext />);
10484
const error = container.querySelector('.graphiql-doc-explorer-error');
105-
10685
expect(error).toHaveTextContent('Error fetching schema');
10786

108-
rerender(
109-
<SchemaContext.Provider value={defaultSchemaContext}>
110-
<DocExplorerWithContext />,
111-
</SchemaContext.Provider>,
112-
);
113-
87+
act(() => {
88+
schemaStore.setState(defaultSchemaContext);
89+
});
90+
rerender(<DocExplorerWithContext />);
11491
const errors = container.querySelectorAll('.graphiql-doc-explorer-error');
11592
expect(errors).toHaveLength(0);
11693
});
@@ -133,48 +110,43 @@ describe('DocExplorer', () => {
133110
};
134111

135112
// Initial render, set initial state
113+
schemaStore.setState({
114+
...defaultSchemaContext,
115+
schema: initialSchema,
116+
});
136117
const { container, rerender } = render(
137-
<SchemaContext.Provider
138-
value={{
139-
...defaultSchemaContext,
140-
schema: initialSchema,
141-
}}
142-
>
143-
<DocExplorerContextProvider>
144-
<SetInitialStack />
145-
</DocExplorerContextProvider>
146-
</SchemaContext.Provider>,
118+
<DocExplorerContextProvider>
119+
<SetInitialStack />
120+
</DocExplorerContextProvider>,
147121
);
148122

149123
// First proper render of doc explorer
124+
act(() => {
125+
schemaStore.setState({
126+
...defaultSchemaContext,
127+
schema: initialSchema,
128+
});
129+
});
150130
rerender(
151-
<SchemaContext.Provider
152-
value={{
153-
...defaultSchemaContext,
154-
schema: initialSchema,
155-
}}
156-
>
157-
<DocExplorerContextProvider>
158-
<DocExplorer />
159-
</DocExplorerContextProvider>
160-
</SchemaContext.Provider>,
131+
<DocExplorerContextProvider>
132+
<DocExplorer />
133+
</DocExplorerContextProvider>,
161134
);
162135

163136
const [title] = container.querySelectorAll('.graphiql-doc-explorer-title');
164137
expect(title.textContent).toEqual('field');
165138

166139
// Second render of doc explorer, this time with a new schema, with _same_ field name
140+
act(() => {
141+
schemaStore.setState({
142+
...defaultSchemaContext,
143+
schema: makeSchema(), // <<< New, but equivalent, schema
144+
});
145+
});
167146
rerender(
168-
<SchemaContext.Provider
169-
value={{
170-
...defaultSchemaContext,
171-
schema: makeSchema(), // <<< New, but equivalent, schema
172-
}}
173-
>
174-
<DocExplorerContextProvider>
175-
<DocExplorer />
176-
</DocExplorerContextProvider>
177-
</SchemaContext.Provider>,
147+
<DocExplorerContextProvider>
148+
<DocExplorer />
149+
</DocExplorerContextProvider>,
178150
);
179151
const [title2] = container.querySelectorAll('.graphiql-doc-explorer-title');
180152
// Because `Query.field` still exists in the new schema, we can still render it
@@ -200,48 +172,43 @@ describe('DocExplorer', () => {
200172
};
201173

202174
// Initial render, set initial state
175+
schemaStore.setState({
176+
...defaultSchemaContext,
177+
schema: initialSchema,
178+
});
203179
const { container, rerender } = render(
204-
<SchemaContext.Provider
205-
value={{
206-
...defaultSchemaContext,
207-
schema: initialSchema,
208-
}}
209-
>
210-
<DocExplorerContextProvider>
211-
<SetInitialStack />
212-
</DocExplorerContextProvider>
213-
</SchemaContext.Provider>,
180+
<DocExplorerContextProvider>
181+
<SetInitialStack />
182+
</DocExplorerContextProvider>,
214183
);
215184

216185
// First proper render of doc explorer
186+
act(() => {
187+
schemaStore.setState({
188+
...defaultSchemaContext,
189+
schema: initialSchema,
190+
});
191+
});
217192
rerender(
218-
<SchemaContext.Provider
219-
value={{
220-
...defaultSchemaContext,
221-
schema: initialSchema,
222-
}}
223-
>
224-
<DocExplorerContextProvider>
225-
<DocExplorer />
226-
</DocExplorerContextProvider>
227-
</SchemaContext.Provider>,
193+
<DocExplorerContextProvider>
194+
<DocExplorer />
195+
</DocExplorerContextProvider>,
228196
);
229197

230198
const [title] = container.querySelectorAll('.graphiql-doc-explorer-title');
231199
expect(title.textContent).toEqual('field');
232200

233-
// Second render of doc explorer, this time with a new schema, with different field name
201+
// Second render of doc explorer, this time with a new schema, with a different field name
202+
act(() => {
203+
schemaStore.setState({
204+
...defaultSchemaContext,
205+
schema: makeSchema('field2'), // <<< New schema with a new field name
206+
});
207+
});
234208
rerender(
235-
<SchemaContext.Provider
236-
value={{
237-
...defaultSchemaContext,
238-
schema: makeSchema('field2'), // <<< New schema with a new field name
239-
}}
240-
>
241-
<DocExplorerContextProvider>
242-
<DocExplorer />
243-
</DocExplorerContextProvider>
244-
</SchemaContext.Provider>,
209+
<DocExplorerContextProvider>
210+
<DocExplorer />
211+
</DocExplorerContextProvider>,
245212
);
246213
const [title2] = container.querySelectorAll('.graphiql-doc-explorer-title');
247214
// Because `Query.field` doesn't exist anymore, the top-most item we can render is `Query`

0 commit comments

Comments
 (0)