1+ import {
2+ createContext ,
3+ ReactNode ,
4+ RefObject ,
5+ useContext ,
6+ useEffect ,
7+ useRef ,
8+ } from 'react' ;
9+ import { createStore , StoreApi , useStore } from 'zustand' ;
110import { HistoryStore , QueryStoreItem , StorageAPI } from '@graphiql/toolkit' ;
2- import { ReactNode , useEffect , useState } from 'react' ;
311import {
412 useStorageContext ,
5- createNullableContext ,
6- createContextHook ,
713 useExecutionContext ,
814 useEditorContext ,
915} from '@graphiql/react' ;
1016
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 = {
2354 /**
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.
3156 */
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 : {
3498 query ?: string ;
3599 variables ?: string ;
36100 headers ?: string ;
37101 operationName ?: string ;
38102 label ?: string ;
39103 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+ } ;
74118} ;
75119
76- export const HistoryContext =
77- createNullableContext < HistoryContextType > ( 'HistoryContext' ) ;
120+ const HistoryContext = createContext < RefObject <
121+ StoreApi < HistoryContextType >
122+ > | null > ( null ) ;
78123
79124type HistoryContextProviderProps = {
80125 children : ReactNode ;
@@ -92,60 +137,46 @@ type HistoryContextProviderProps = {
92137 * to a backend instead of localStorage and might need an id property added to the QueryStoreItem)
93138 */
94139export function HistoryContextProvider ( {
95- maxHistoryLength = DEFAULT_HISTORY_LENGTH ,
140+ maxHistoryLength = 20 ,
96141 children,
97142} : HistoryContextProviderProps ) {
98143 const storage = useStorageContext ( ) ;
99144 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- } ;
127145 const { tabs, activeTabIndex } = useEditorContext ( { nonNull : true } ) ;
128146 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+ }
130152
131153 useEffect ( ( ) => {
132154 if ( ! isFetching ) {
133155 return ;
134156 }
157+ const { addToHistory } = storeRef . current . getState ( ) . actions ;
135158 addToHistory ( {
136159 query : activeTab . query ?? undefined ,
137160 variables : activeTab . variables ?? undefined ,
138161 headers : activeTab . headers ?? undefined ,
139162 operationName : activeTab . operationName ?? undefined ,
140163 } ) ;
141- } , [ isFetching , activeTab , addToHistory ] ) ;
164+ } , [ isFetching , activeTab ] ) ;
142165
143166 return (
144- < HistoryContext . Provider value = { value } > { children } </ HistoryContext . Provider >
167+ < HistoryContext . Provider value = { storeRef } >
168+ { children }
169+ </ HistoryContext . Provider >
145170 ) ;
146171}
147172
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+ }
150180
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