1
+ import {
2
+ createContext ,
3
+ ReactNode ,
4
+ RefObject ,
5
+ useContext ,
6
+ useEffect ,
7
+ useRef ,
8
+ } from 'react' ;
9
+ import { createStore , StoreApi , useStore } from 'zustand' ;
1
10
import { HistoryStore , QueryStoreItem , StorageAPI } from '@graphiql/toolkit' ;
2
- import { ReactNode , useEffect , useState } from 'react' ;
3
11
import {
4
12
useStorageContext ,
5
- createNullableContext ,
6
- createContextHook ,
7
13
useExecutionContext ,
8
14
useEditorContext ,
9
15
} from '@graphiql/react' ;
10
16
11
- export type HistoryContextType = {
12
- /**
13
- * Add an operation to the history.
14
- * @param operation The operation that was executed, consisting of the query,
15
- * variables, headers, and operation name.
16
- */
17
- addToHistory ( operation : {
18
- query ?: string ;
19
- variables ?: string ;
20
- headers ?: string ;
21
- operationName ?: string ;
22
- } ) : void ;
17
+ function createHistoryStore (
18
+ storage : StorageAPI | null ,
19
+ maxHistoryLength : number ,
20
+ ) {
21
+ const historyStore =
22
+ // Fall back to a noop storage when the StorageContext is empty
23
+ new HistoryStore ( storage || new StorageAPI ( null ) , maxHistoryLength ) ;
24
+
25
+ return createStore < HistoryContextType > ( set => ( {
26
+ items : historyStore . queries ,
27
+ actions : {
28
+ addToHistory ( operation ) {
29
+ historyStore . updateHistory ( operation ) ;
30
+ const items = historyStore . queries ;
31
+ set ( { items } ) ;
32
+ } ,
33
+ editLabel ( operation , index ) {
34
+ historyStore . editLabel ( operation , index ) ;
35
+ const items = historyStore . queries ;
36
+ set ( { items } ) ;
37
+ } ,
38
+ toggleFavorite ( operation ) {
39
+ historyStore . toggleFavorite ( operation ) ;
40
+ const items = historyStore . queries ;
41
+ set ( { items } ) ;
42
+ } ,
43
+ setActive : item => item ,
44
+ deleteFromHistory ( item , clearFavorites ) {
45
+ historyStore . deleteHistory ( item , clearFavorites ) ;
46
+ const items = historyStore . queries ;
47
+ set ( { items } ) ;
48
+ } ,
49
+ } ,
50
+ } ) ) ;
51
+ }
52
+
53
+ type HistoryContextType = {
23
54
/**
24
- * Change the custom label of an item from the history.
25
- * @param args An object containing the label (`undefined` if it should be
26
- * unset) and properties that identify the history item that the label should
27
- * be applied to. (This can result in the label being applied to multiple
28
- * history items.)
29
- * @param index Index to edit. Without it, will look for the first index matching the
30
- * operation, which may lead to misleading results if multiple items have the same label
55
+ * The list of history items.
31
56
*/
32
- editLabel (
33
- args : {
57
+ items : readonly QueryStoreItem [ ] ;
58
+ actions : {
59
+ /**
60
+ * Add an operation to the history.
61
+ * @param operation The operation that was executed, consisting of the query,
62
+ * variables, headers, and operation name.
63
+ */
64
+ addToHistory ( operation : {
65
+ query ?: string ;
66
+ variables ?: string ;
67
+ headers ?: string ;
68
+ operationName ?: string ;
69
+ } ) : void ;
70
+ /**
71
+ * Change the custom label of an item from the history.
72
+ * @param args An object containing the label (`undefined` if it should be
73
+ * unset) and properties that identify the history item that the label should
74
+ * be applied to. (This can result in the label being applied to multiple
75
+ * history items.)
76
+ * @param index Index to edit. Without it, will look for the first index matching the
77
+ * operation, which may lead to misleading results if multiple items have the same label
78
+ */
79
+ editLabel (
80
+ args : {
81
+ query ?: string ;
82
+ variables ?: string ;
83
+ headers ?: string ;
84
+ operationName ?: string ;
85
+ label ?: string ;
86
+ favorite ?: boolean ;
87
+ } ,
88
+ index ?: number ,
89
+ ) : void ;
90
+ /**
91
+ * Toggle the favorite state of an item from the history.
92
+ * @param args An object containing the favorite state (`undefined` if it
93
+ * should be unset) and properties that identify the history item that the
94
+ * label should be applied to. (This can result in the label being applied
95
+ * to multiple history items.)
96
+ */
97
+ toggleFavorite ( args : {
34
98
query ?: string ;
35
99
variables ?: string ;
36
100
headers ?: string ;
37
101
operationName ?: string ;
38
102
label ?: string ;
39
103
favorite ?: boolean ;
40
- } ,
41
- index ?: number ,
42
- ) : void ;
43
- /**
44
- * The list of history items.
45
- */
46
- items : readonly QueryStoreItem [ ] ;
47
- /**
48
- * Toggle the favorite state of an item from the history.
49
- * @param args An object containing the favorite state (`undefined` if it
50
- * should be unset) and properties that identify the history item that the
51
- * label should be applied to. (This can result in the label being applied
52
- * to multiple history items.)
53
- */
54
- toggleFavorite ( args : {
55
- query ?: string ;
56
- variables ?: string ;
57
- headers ?: string ;
58
- operationName ?: string ;
59
- label ?: string ;
60
- favorite ?: boolean ;
61
- } ) : void ;
62
- /**
63
- * Delete an operation from the history.
64
- * @param args The operation that was executed, consisting of the query,
65
- * variables, headers, and operation name.
66
- * @param clearFavorites This is only if you press the 'clear' button
67
- */
68
- deleteFromHistory ( args : QueryStoreItem , clearFavorites ?: boolean ) : void ;
69
- /**
70
- * If you need to know when an item in history is set as active to customize
71
- * your application.
72
- */
73
- setActive ( args : QueryStoreItem ) : void ;
104
+ } ) : void ;
105
+ /**
106
+ * Delete an operation from the history.
107
+ * @param args The operation that was executed, consisting of the query,
108
+ * variables, headers, and operation name.
109
+ * @param clearFavorites This is only if you press the 'clear' button
110
+ */
111
+ deleteFromHistory ( args : QueryStoreItem , clearFavorites ?: boolean ) : void ;
112
+ /**
113
+ * If you need to know when an item in history is set as active to customize
114
+ * your application.
115
+ */
116
+ setActive ( args : QueryStoreItem ) : void ;
117
+ } ;
74
118
} ;
75
119
76
- export const HistoryContext =
77
- createNullableContext < HistoryContextType > ( 'HistoryContext' ) ;
120
+ const HistoryContext = createContext < RefObject <
121
+ StoreApi < HistoryContextType >
122
+ > | null > ( null ) ;
78
123
79
124
type HistoryContextProviderProps = {
80
125
children : ReactNode ;
@@ -92,60 +137,46 @@ type HistoryContextProviderProps = {
92
137
* to a backend instead of localStorage and might need an id property added to the QueryStoreItem)
93
138
*/
94
139
export function HistoryContextProvider ( {
95
- maxHistoryLength = DEFAULT_HISTORY_LENGTH ,
140
+ maxHistoryLength = 20 ,
96
141
children,
97
142
} : HistoryContextProviderProps ) {
98
143
const storage = useStorageContext ( ) ;
99
144
const { isFetching } = useExecutionContext ( { nonNull : true } ) ;
100
- const [ historyStore ] = useState (
101
- ( ) =>
102
- // Fall back to a noop storage when the StorageContext is empty
103
- new HistoryStore ( storage || new StorageAPI ( null ) , maxHistoryLength ) ,
104
- ) ;
105
- const [ items , setItems ] = useState ( ( ) => historyStore . queries || [ ] ) ;
106
-
107
- const value : HistoryContextType = {
108
- addToHistory ( operation ) {
109
- historyStore . updateHistory ( operation ) ;
110
- setItems ( historyStore . queries ) ;
111
- } ,
112
- editLabel ( operation , index ) {
113
- historyStore . editLabel ( operation , index ) ;
114
- setItems ( historyStore . queries ) ;
115
- } ,
116
- items,
117
- toggleFavorite ( operation ) {
118
- historyStore . toggleFavorite ( operation ) ;
119
- setItems ( historyStore . queries ) ;
120
- } ,
121
- setActive : item => item ,
122
- deleteFromHistory ( item , clearFavorites ) {
123
- historyStore . deleteHistory ( item , clearFavorites ) ;
124
- setItems ( historyStore . queries ) ;
125
- } ,
126
- } ;
127
145
const { tabs, activeTabIndex } = useEditorContext ( { nonNull : true } ) ;
128
146
const activeTab = tabs [ activeTabIndex ] ;
129
- const { addToHistory } = value ;
147
+ const storeRef = useRef < StoreApi < HistoryContextType > > ( null ! ) ;
148
+
149
+ if ( storeRef . current === null ) {
150
+ storeRef . current = createHistoryStore ( storage , maxHistoryLength ) ;
151
+ }
130
152
131
153
useEffect ( ( ) => {
132
154
if ( ! isFetching ) {
133
155
return ;
134
156
}
157
+ const { addToHistory } = storeRef . current . getState ( ) . actions ;
135
158
addToHistory ( {
136
159
query : activeTab . query ?? undefined ,
137
160
variables : activeTab . variables ?? undefined ,
138
161
headers : activeTab . headers ?? undefined ,
139
162
operationName : activeTab . operationName ?? undefined ,
140
163
} ) ;
141
- } , [ isFetching , activeTab , addToHistory ] ) ;
164
+ } , [ isFetching , activeTab ] ) ;
142
165
143
166
return (
144
- < HistoryContext . Provider value = { value } > { children } </ HistoryContext . Provider >
167
+ < HistoryContext . Provider value = { storeRef } >
168
+ { children }
169
+ </ HistoryContext . Provider >
145
170
) ;
146
171
}
147
172
148
- export const useHistoryContext =
149
- createContextHook < HistoryContextType > ( HistoryContext ) ;
173
+ function useHistoryStore < T > ( selector : ( state : HistoryContextType ) => T ) : T {
174
+ const store = useContext ( HistoryContext ) ;
175
+ if ( ! store ) {
176
+ throw new Error ( 'Missing `HistoryContextProvider` in the tree' ) ;
177
+ }
178
+ return useStore ( store . current , selector ) ;
179
+ }
150
180
151
- const DEFAULT_HISTORY_LENGTH = 20 ;
181
+ export const useHistory = ( ) => useHistoryStore ( state => state . items ) ;
182
+ export const useHistoryActions = ( ) => useHistoryStore ( state => state . actions ) ;
0 commit comments