Skip to content

Commit b697fec

Browse files
committed
[wip] fix(useQuery): use setOptions for updating query. Thanks to that previous value of data is shown while loading new one and networkStatus should have a correct value in that case
BREAKING CHANGE: previous value of `data` is shown while loading new one Closes #117 Closes #121 Closes #129
1 parent e590e8f commit b697fec

File tree

1 file changed

+75
-25
lines changed

1 file changed

+75
-25
lines changed

src/useQuery.ts

Lines changed: 75 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ import ApolloClient, {
1212
WatchQueryOptions,
1313
} from 'apollo-client';
1414
import { DocumentNode } from 'graphql';
15-
import { useContext, useEffect, useMemo, useState } from 'react';
15+
import pick from 'lodash/pick';
16+
import { useContext, useEffect, useMemo, useRef, useState } from 'react';
17+
1618
import { useApolloClient } from './ApolloContext';
1719
import { SSRContext } from './internal/SSRContext';
1820
import actHack from './internal/actHack';
@@ -123,32 +125,89 @@ export function useQuery<
123125
fetchResults,
124126
]
125127
);
126-
const observableQuery = useMemo(
127-
() =>
128-
getCachedObservableQuery<TData, TVariables>(client, watchQueryOptions),
129-
[client, watchQueryOptions]
128+
129+
const previousClient = useRef<ApolloClient<TCache>>(client);
130+
const previousQuery = useRef<DocumentNode>(query);
131+
const observableQuery = useRef<ObservableQuery<TData, TVariables> | null>(
132+
null
130133
);
134+
const isObservableQueryInitialized = useRef<boolean>(false);
135+
136+
if (previousClient.current !== client || previousQuery.current !== query) {
137+
observableQuery.current = null;
138+
isObservableQueryInitialized.current = false;
139+
140+
if (previousClient.current !== client) {
141+
previousClient.current = client;
142+
}
143+
144+
if (previousQuery.current !== query) {
145+
previousQuery.current = query;
146+
}
147+
}
131148

132149
const [responseId, setResponseId] = useState(0);
133150

151+
const invalidateCurrentResult = () => {
152+
// A hack to get rid React warnings during tests. The default
153+
// implementation of `actHack` just invokes the callback immediately.
154+
// In tests, it's replaced with `act` from react-testing-library.
155+
// A better solution welcome.
156+
actHack(() => {
157+
setResponseId(x => x + 1);
158+
});
159+
};
160+
161+
let currentObservableQuery: ObservableQuery<TData, TVariables>;
162+
if (observableQuery.current) {
163+
currentObservableQuery = observableQuery.current;
164+
} else {
165+
currentObservableQuery = getCachedObservableQuery<TData, TVariables>(
166+
client,
167+
watchQueryOptions
168+
);
169+
observableQuery.current = currentObservableQuery;
170+
}
171+
172+
useEffect(
173+
() => {
174+
if (isObservableQueryInitialized.current) {
175+
currentObservableQuery.setOptions(
176+
pick(watchQueryOptions, [
177+
'errorPolicy',
178+
'fetchPolicy',
179+
'fetchResults',
180+
'notifyOnNetworkStatusChange',
181+
'pollInterval',
182+
'variables',
183+
])
184+
);
185+
invalidateCurrentResult();
186+
} else {
187+
isObservableQueryInitialized.current = true;
188+
}
189+
},
190+
[currentObservableQuery, watchQueryOptions]
191+
);
192+
134193
const currentResult = useMemo<QueryHookResult<TData, TVariables>>(
135194
() => {
136195
const helpers = {
137-
fetchMore: observableQuery.fetchMore.bind(observableQuery),
138-
refetch: observableQuery.refetch.bind(observableQuery),
139-
startPolling: observableQuery.startPolling.bind(observableQuery),
140-
stopPolling: observableQuery.stopPolling.bind(observableQuery),
141-
updateQuery: observableQuery.updateQuery.bind(observableQuery),
196+
fetchMore: currentObservableQuery.fetchMore.bind(observableQuery),
197+
refetch: currentObservableQuery.refetch.bind(observableQuery),
198+
startPolling: currentObservableQuery.startPolling.bind(observableQuery),
199+
stopPolling: currentObservableQuery.stopPolling.bind(observableQuery),
200+
updateQuery: currentObservableQuery.updateQuery.bind(observableQuery),
142201
};
143202

144-
const result = observableQuery.currentResult();
203+
const result = currentObservableQuery.currentResult();
145204

146205
// return the old result data when there is an error
147206
let data = result.data as TData;
148207
if (result.error || result.errors) {
149208
data = {
150209
...result.data,
151-
...(observableQuery.getLastResult() || {}).data,
210+
...(currentObservableQuery.getLastResult() || {}).data,
152211
};
153212
}
154213

@@ -179,7 +238,7 @@ export function useQuery<
179238
partial: result.partial,
180239
};
181240
},
182-
[shouldSkip, responseId, observableQuery]
241+
[shouldSkip, responseId, currentObservableQuery]
183242
);
184243

185244
useEffect(
@@ -188,16 +247,7 @@ export function useQuery<
188247
return;
189248
}
190249

191-
const invalidateCurrentResult = () => {
192-
// A hack to get rid React warnings during tests. The default
193-
// implementation of `actHack` just invokes the callback immediately.
194-
// In tests, it's replaced with `act` from react-testing-library.
195-
// A better solution welcome.
196-
actHack(() => {
197-
setResponseId(x => x + 1);
198-
});
199-
};
200-
const subscription = observableQuery.subscribe(
250+
const subscription = currentObservableQuery.subscribe(
201251
invalidateCurrentResult,
202252
invalidateCurrentResult
203253
);
@@ -217,11 +267,11 @@ export function useQuery<
217267
if (suspend) {
218268
// throw a promise - use the react suspense to wait until the data is
219269
// available
220-
throw observableQuery.result();
270+
throw currentObservableQuery.result();
221271
}
222272

223273
if (ssrInUse) {
224-
ssrManager!.register(observableQuery.result());
274+
ssrManager!.register(currentObservableQuery.result());
225275
}
226276
}
227277

0 commit comments

Comments
 (0)