Skip to content

Commit fa78481

Browse files
authored
refactor useStorage, useDocExplorer and useHistory hooks and their stores (#3947)
* upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * changeset * upd * upd * fix tests * fix example * fix tests * upd * upd * upd * upd * upd * less git lines * fix tests * fix tests * lint * fix build * fix build * fix cspell * storage * storage * storage * storage * storage * upd * upd * upd * upd * upd * upd * changeset * upd * fix unit test * rm so ts expect error * upd * fix tabs tests * fix history tests
1 parent 117627b commit fa78481

File tree

27 files changed

+242
-358
lines changed

27 files changed

+242
-358
lines changed

.changeset/itchy-spies-develop.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'@graphiql/plugin-doc-explorer': minor
3+
'@graphiql/plugin-explorer': patch
4+
'@graphiql/plugin-history': minor
5+
'@graphiql/react': minor
6+
'graphiql': patch
7+
---
8+
9+
refactor `useStorage`, `useDocExplorer` and `useHistory` hooks

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export const PREV_URLS_KEY = 'previousURLs';
99

1010
const SelectServer = ({ url, setUrl }) => {
1111
const inputRef = React.useRef(null);
12-
const storage = useStorage({ nonNull: true });
12+
const storage = useStorage();
1313
const lastUrl = storage.get(LAST_URL_KEY);
1414
const currentUrl = lastUrl ?? url;
1515
const [inputValue, setInputValue] = React.useState(currentUrl);

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"scripts": {
1717
"types:check": "turbo run types:check",
1818
"dev:graphiql": "turbo run dev --filter=graphiql...",
19+
"build:graphiql": "turbo run build --filter=graphiql...",
1920
"build": "yarn build-clean && yarn tsc && yarn build:nontsc",
2021
"build-bundles": "yarn prebuild-bundles && yarn wsrun:noexamples --stages build-bundles",
2122
"build-bundles-clean": "rimraf '{packages,examples}/**/{bundle,cdn,webpack}'",

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ describe('DocExplorer', () => {
195195
</DocExplorerContextProvider>,
196196
);
197197

198-
const [title] = container.querySelectorAll('.graphiql-doc-explorer-title');
198+
const title = container.querySelector('.graphiql-doc-explorer-title')!;
199199
expect(title.textContent).toEqual('field');
200200

201201
// Second render of doc explorer, this time with a new schema, with a different field name
@@ -210,7 +210,7 @@ describe('DocExplorer', () => {
210210
<DocExplorer />
211211
</DocExplorerContextProvider>,
212212
);
213-
const [title2] = container.querySelectorAll('.graphiql-doc-explorer-title');
213+
const title2 = container.querySelector('.graphiql-doc-explorer-title')!;
214214
// Because `Query.field` doesn't exist anymore, the top-most item we can render is `Query`
215215
expect(title2.textContent).toEqual('Query');
216216
});

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

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
import { FC } from 'react';
1+
import { FC, useEffect } from 'react';
22
import { fireEvent, render } from '@testing-library/react';
33
import { GraphQLString, GraphQLObjectType, Kind } from 'graphql';
4-
import { DocExplorerContext, DocExplorerFieldDef } from '../../context';
4+
import { DocExplorerFieldDef, docExplorerStore } from '../../context';
55
import { FieldDocumentation } from '../field-documentation';
6-
import { useMockDocExplorerContextValue } from './test-utils';
76

87
const exampleObject = new GraphQLObjectType({
98
name: 'Query',
@@ -55,17 +54,19 @@ const exampleObject = new GraphQLObjectType({
5554

5655
const FieldDocumentationWithContext: FC<{
5756
field: DocExplorerFieldDef;
58-
}> = props => {
59-
return (
60-
<DocExplorerContext.Provider
61-
value={useMockDocExplorerContextValue({
62-
name: props.field.name,
63-
def: props.field,
64-
})}
65-
>
66-
<FieldDocumentation field={props.field} />
67-
</DocExplorerContext.Provider>
68-
);
57+
}> = ({ field }) => {
58+
useEffect(() => {
59+
docExplorerStore.setState({
60+
explorerNavStack: [
61+
{
62+
name: field.name,
63+
def: field,
64+
},
65+
],
66+
});
67+
}, [field]);
68+
69+
return <FieldDocumentation field={field} />;
6970
};
7071

7172
describe('FieldDocumentation', () => {

packages/graphiql-plugin-doc-explorer/src/components/__tests__/test-utils.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,5 @@
11
'use no memo';
2-
import { useRef } from 'react';
32
import { GraphQLNamedType, GraphQLType } from 'graphql';
4-
import { createDocExplorerStore, DocExplorerNavStackItem } from '../../context';
5-
6-
export function useMockDocExplorerContextValue(
7-
navStackItem: DocExplorerNavStackItem,
8-
) {
9-
return useRef(createDocExplorerStore(navStackItem));
10-
}
113

124
export function unwrapType(type: GraphQLType): GraphQLNamedType {
135
return 'ofType' in type ? unwrapType(type.ofType) : type;

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

Lines changed: 19 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { FC } from 'react';
1+
import { FC, useEffect } from 'react';
22
import { fireEvent, render } from '@testing-library/react';
33
import { GraphQLNamedType } from 'graphql';
44
import {
@@ -7,31 +7,26 @@ import {
77
ExampleUnion,
88
ExampleQuery,
99
} from './fixtures';
10-
import { DocExplorerContext } from '../../context';
10+
import { docExplorerStore } from '../../context';
1111
import { TypeDocumentation } from '../type-documentation';
12-
import { useMockDocExplorerContextValue, unwrapType } from './test-utils';
12+
import { unwrapType } from './test-utils';
13+
import { schemaStore } from '../../../../graphiql-react/dist/schema';
1314

14-
vi.mock('@graphiql/react', async () => {
15-
const actual = await vi.importActual('@graphiql/react');
16-
return {
17-
...actual,
18-
useSchemaStore: () => ({
19-
schema: ExampleSchema,
20-
}),
21-
};
22-
});
23-
24-
const TypeDocumentationWithContext: FC<{ type: GraphQLNamedType }> = props => {
25-
return (
26-
<DocExplorerContext.Provider
27-
value={useMockDocExplorerContextValue({
28-
name: unwrapType(props.type).name,
29-
def: props.type,
30-
})}
31-
>
32-
<TypeDocumentation type={props.type} />
33-
</DocExplorerContext.Provider>
34-
);
15+
const TypeDocumentationWithContext: FC<{ type: GraphQLNamedType }> = ({
16+
type,
17+
}) => {
18+
useEffect(() => {
19+
schemaStore.setState({ schema: ExampleSchema });
20+
docExplorerStore.setState({
21+
explorerNavStack: [
22+
{
23+
name: unwrapType(type).name,
24+
def: type,
25+
},
26+
],
27+
});
28+
}, [type]);
29+
return <TypeDocumentation type={type} />;
3530
};
3631

3732
describe('TypeDocumentation', () => {

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

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { FC } from 'react';
1+
import { FC, useEffect } from 'react';
22
import { fireEvent, render } from '@testing-library/react';
33
import { GraphQLNonNull, GraphQLList, GraphQLString } from 'graphql';
4-
import { DocExplorerContext, useDocExplorer } from '../../context';
4+
import { docExplorerStore, useDocExplorer } from '../../context';
55
import { TypeLink } from '../type-link';
6-
import { useMockDocExplorerContextValue, unwrapType } from './test-utils';
6+
import { unwrapType } from './test-utils';
77

88
const nonNullType = new GraphQLNonNull(GraphQLString);
99
const listType = new GraphQLList(GraphQLString);
@@ -17,18 +17,24 @@ const TypeLinkConsumer: FC = () => {
1717
);
1818
};
1919

20-
const TypeLinkWithContext: typeof TypeLink = props => {
20+
const TypeLinkWithContext: typeof TypeLink = ({ type }) => {
21+
useEffect(() => {
22+
docExplorerStore.setState({
23+
explorerNavStack: [
24+
{
25+
name: unwrapType(type).name,
26+
def: unwrapType(type),
27+
},
28+
],
29+
});
30+
}, [type]);
31+
2132
return (
22-
<DocExplorerContext.Provider
23-
value={useMockDocExplorerContextValue({
24-
name: unwrapType(props.type).name,
25-
def: unwrapType(props.type),
26-
})}
27-
>
28-
<TypeLink {...props} />
33+
<>
34+
<TypeLink type={type} />
2935
{/* Print the top of the current nav stack for test assertions */}
3036
<TypeLinkConsumer />
31-
</DocExplorerContext.Provider>
37+
</>
3238
);
3339
};
3440

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -200,16 +200,16 @@ const EnumValue: FC<{ value: GraphQLEnumValue }> = ({ value }) => {
200200
return (
201201
<div className="graphiql-doc-explorer-item">
202202
<div className="graphiql-doc-explorer-enum-value">{value.name}</div>
203-
{value.description ? (
203+
{value.description && (
204204
<MarkdownContent type="description">
205205
{value.description}
206206
</MarkdownContent>
207-
) : null}
208-
{value.deprecationReason ? (
207+
)}
208+
{value.deprecationReason && (
209209
<MarkdownContent type="deprecation">
210210
{value.deprecationReason}
211211
</MarkdownContent>
212-
) : null}
212+
)}
213213
</div>
214214
);
215215
};

packages/graphiql-plugin-doc-explorer/src/context.tsx renamed to packages/graphiql-plugin-doc-explorer/src/context.ts

Lines changed: 18 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,9 @@ import {
1414
isScalarType,
1515
isUnionType,
1616
} from 'graphql';
17-
import {
18-
createContext,
19-
FC,
20-
ReactNode,
21-
RefObject,
22-
useContext,
23-
useEffect,
24-
useRef,
25-
} from 'react';
17+
import { FC, ReactElement, ReactNode, useEffect } from 'react';
2618
import { SchemaContextType, useSchemaStore } from '@graphiql/react';
27-
import { createStore, StoreApi, useStore } from 'zustand';
19+
import { createStore, useStore } from 'zustand';
2820

2921
export type DocExplorerFieldDef =
3022
| GraphQLField<unknown, unknown>
@@ -80,11 +72,11 @@ export type DocExplorerContextType = {
8072
};
8173
};
8274

83-
export function createDocExplorerStore(
84-
initialNavStackItem: DocExplorerNavStackItem = { name: 'Docs' },
85-
) {
86-
return createStore<DocExplorerContextType>((set, get) => ({
87-
explorerNavStack: [initialNavStackItem],
75+
const INITIAL_NAV_STACK: DocExplorerNavStack = [{ name: 'Docs' }];
76+
77+
export const docExplorerStore = createStore<DocExplorerContextType>(
78+
(set, get) => ({
79+
explorerNavStack: INITIAL_NAV_STACK,
8880
actions: {
8981
push(item) {
9082
set(state => {
@@ -110,8 +102,7 @@ export function createDocExplorerStore(
110102
reset() {
111103
set(state => {
112104
const curr = state.explorerNavStack;
113-
const explorerNavStack: DocExplorerNavStack =
114-
curr.length === 1 ? curr : [initialNavStackItem];
105+
const explorerNavStack = curr.length === 1 ? curr : INITIAL_NAV_STACK;
115106
return { explorerNavStack };
116107
});
117108
},
@@ -161,13 +152,14 @@ export function createDocExplorerStore(
161152
if (oldNavStack.length === 1) {
162153
return oldNavStack;
163154
}
164-
const newNavStack: DocExplorerNavStack = [initialNavStackItem];
155+
// Spread is needed
156+
const newNavStack: DocExplorerNavStack = [...INITIAL_NAV_STACK];
165157
let lastEntity:
166158
| GraphQLNamedType
167159
| GraphQLField<unknown, unknown>
168160
| null = null;
169161
for (const item of oldNavStack) {
170-
if (item === initialNavStackItem) {
162+
if (item === INITIAL_NAV_STACK[0]) {
171163
// No need to copy the initial item
172164
continue;
173165
}
@@ -236,32 +228,23 @@ export function createDocExplorerStore(
236228
});
237229
},
238230
},
239-
}));
240-
}
241-
242-
export const DocExplorerContext = createContext<RefObject<
243-
StoreApi<DocExplorerContextType>
244-
> | null>(null);
231+
}),
232+
);
245233

246234
export const DocExplorerContextProvider: FC<{
247235
children: ReactNode;
248-
}> = props => {
236+
}> = ({ children }) => {
249237
const { schema, validationErrors, schemaReference } = useSchemaStore();
250-
const storeRef = useRef<StoreApi<DocExplorerContextType>>(null!);
251-
252-
if (storeRef.current === null) {
253-
storeRef.current = createDocExplorerStore();
254-
}
255238

256239
useEffect(() => {
257240
const { resolveSchemaReferenceToNavItem } =
258-
storeRef.current.getState().actions;
241+
docExplorerStore.getState().actions;
259242
resolveSchemaReferenceToNavItem(schemaReference);
260243
}, [schemaReference]);
261244

262245
useEffect(() => {
263246
const { reset, rebuildNavStackWithSchema } =
264-
storeRef.current.getState().actions;
247+
docExplorerStore.getState().actions;
265248

266249
// Whenever the schema changes, we must revalidate/replace the nav stack.
267250
if (schema == null || validationErrors.length > 0) {
@@ -271,21 +254,13 @@ export const DocExplorerContextProvider: FC<{
271254
}
272255
}, [schema, validationErrors]);
273256

274-
return (
275-
<DocExplorerContext.Provider value={storeRef}>
276-
{props.children}
277-
</DocExplorerContext.Provider>
278-
);
257+
return children as ReactElement;
279258
};
280259

281260
function useDocExplorerStore<T>(
282261
selector: (state: DocExplorerContextType) => T,
283262
): T {
284-
const store = useContext(DocExplorerContext);
285-
if (!store) {
286-
throw new Error('Missing `DocExplorerContextProvider` in the tree');
287-
}
288-
return useStore(store.current, selector);
263+
return useStore(docExplorerStore, selector);
289264
}
290265

291266
export const useDocExplorer = () =>

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import { DocExplorer } from './components';
99
export * from './components';
1010

1111
export {
12-
DocExplorerContext,
1312
DocExplorerContextProvider,
1413
useDocExplorer,
1514
useDocExplorerActions,

packages/graphiql-plugin-history/src/__tests__/components.spec.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ import { fireEvent, render } from '@testing-library/react';
33
import type { ComponentProps } from 'react';
44
import { formatQuery, HistoryItem } from '../components';
55
import { HistoryContextProvider } from '../context';
6-
import { useEditorContext, Tooltip } from '@graphiql/react';
6+
import {
7+
useEditorContext,
8+
Tooltip,
9+
StorageContextProvider,
10+
} from '@graphiql/react';
711

812
vi.mock('@graphiql/react', async () => {
913
const originalModule = await vi.importActual('@graphiql/react');
@@ -45,9 +49,11 @@ type QueryHistoryItemProps = ComponentProps<typeof HistoryItem>;
4549
const QueryHistoryItemWithContext: typeof HistoryItem = props => {
4650
return (
4751
<Tooltip.Provider>
48-
<HistoryContextProvider>
49-
<HistoryItem {...props} />
50-
</HistoryContextProvider>
52+
<StorageContextProvider>
53+
<HistoryContextProvider>
54+
<HistoryItem {...props} />
55+
</HistoryContextProvider>
56+
</StorageContextProvider>
5157
</Tooltip.Provider>
5258
);
5359
};

0 commit comments

Comments
 (0)