From 08bb2d2db2712f3db15ed8b4439f8398447707fa Mon Sep 17 00:00:00 2001 From: Robert Balicki Date: Thu, 11 Jun 2020 10:31:28 -0700 Subject: [PATCH] Handle case where loadQuery was called with a different environment Reviewed By: josephsavona Differential Revision: D21955516 fbshipit-source-id: 487e87a5b86300c6bfbedca4c57abee9e63a7b63 --- .../EntryPointTypes.flow.js | 1 + .../__tests__/usePreloadedQuery-test.js | 47 +++++++++++++++++++ packages/relay-experimental/loadQuery.js | 14 ++++-- .../relay-experimental/usePreloadedQuery.js | 32 +++++++++---- 4 files changed, 81 insertions(+), 13 deletions(-) diff --git a/packages/relay-experimental/EntryPointTypes.flow.js b/packages/relay-experimental/EntryPointTypes.flow.js index 02edf8d3ed6b9..4c403403e2d3d 100644 --- a/packages/relay-experimental/EntryPointTypes.flow.js +++ b/packages/relay-experimental/EntryPointTypes.flow.js @@ -86,6 +86,7 @@ export type PreloadedQueryInner< +fetchPolicy: PreloadFetchPolicy, +id: ?string, +name: string, + +networkCacheConfig: ?CacheConfig, +source: ?Observable, +kind: 'PreloadedQuery', +variables: $ElementType, diff --git a/packages/relay-experimental/__tests__/usePreloadedQuery-test.js b/packages/relay-experimental/__tests__/usePreloadedQuery-test.js index 203fadeadb813..3d88c70774554 100644 --- a/packages/relay-experimental/__tests__/usePreloadedQuery-test.js +++ b/packages/relay-experimental/__tests__/usePreloadedQuery-test.js @@ -929,5 +929,52 @@ describe('usePreloadedQuery', () => { }); }); }); + + describe('when environments do not match', () => { + it('should fetch the data at render time, even if the query has already resolved', () => { + let altDataSource; + const altFetch = jest.fn((_query, _variables, _cacheConfig) => + Observable.create(sink => { + altDataSource = sink; + }), + ); + const altEnvironment = new Environment({ + network: Network.create(altFetch), + store: new Store(new RecordSource()), + }); + const prefetched = loadQuery(environment, preloadableConcreteRequest, { + id: '4', + }); + let data; + expect(dataSource).toBeDefined(); + if (dataSource) { + dataSource.next(response); + } + TestRenderer.act(() => jest.runAllImmediates()); + + expect(altFetch).not.toHaveBeenCalled(); + function Component(props) { + data = usePreloadedQuery(query, props.prefetched); + return data.node.name; + } + const renderer = TestRenderer.create( + + + + + , + ); + + expect(renderer.toJSON()).toEqual('Fallback'); + expect(altFetch).toHaveBeenCalledTimes(1); + expect(altDataSource).toBeDefined(); + if (altDataSource) { + altDataSource.next(response); + } + + TestRenderer.act(() => jest.runAllImmediates()); + expect(renderer.toJSON()).toEqual('Zuck'); + }); + }); }); }); diff --git a/packages/relay-experimental/loadQuery.js b/packages/relay-experimental/loadQuery.js index 4a4f9c74b1220..bea21efc54316 100644 --- a/packages/relay-experimental/loadQuery.js +++ b/packages/relay-experimental/loadQuery.js @@ -69,6 +69,10 @@ function loadQuery( ); const fetchPolicy = options?.fetchPolicy ?? 'store-or-network'; + const networkCacheConfig = { + ...options?.networkCacheConfig, + force: true, + }; let madeNetworkRequest = false; const makeNetworkRequest = (params): Observable => { @@ -77,10 +81,11 @@ function loadQuery( madeNetworkRequest = true; const network = environment.getNetwork(); - const sourceObservable = network.execute(params, variables, { - force: true, - ...options?.networkCacheConfig, - }); + const sourceObservable = network.execute( + params, + variables, + networkCacheConfig, + ); const subject = new ReplaySubject(); sourceObservable.subscribe({ @@ -213,6 +218,7 @@ function loadQuery( }, id: moduleId, name: params.name, + networkCacheConfig, fetchPolicy, source: madeNetworkRequest ? returnedObservable : undefined, variables, diff --git a/packages/relay-experimental/usePreloadedQuery.js b/packages/relay-experimental/usePreloadedQuery.js index 88950edc9b05f..6b4d63d0103a0 100644 --- a/packages/relay-experimental/usePreloadedQuery.js +++ b/packages/relay-experimental/usePreloadedQuery.js @@ -17,11 +17,11 @@ const invariant = require('invariant'); const useLazyLoadQueryNode = require('./useLazyLoadQueryNode'); const useMemoOperationDescriptor = require('./useMemoOperationDescriptor'); const useRelayEnvironment = require('./useRelayEnvironment'); +const warning = require('warning'); const {useTrackLoadQueryInRender} = require('./loadQuery'); const {useDebugValue} = require('react'); const { - Observable, __internal: {fetchQueryDeduped}, } = require('relay-runtime'); @@ -88,14 +88,28 @@ function usePreloadedQuery( // Thus, if two calls to loadQuery are made with the same environment and identifier // (i.e. the same request is made twice), the second query will be deduped // and components will suspend for the duration of the first query. - const dedupedSource = - source != null - ? fetchQueryDeduped( - environment, - operation.request.identifier, - () => source, - ) - : null; + const dedupedSource = fetchQueryDeduped( + environment, + operation.request.identifier, + () => { + if (source && environment === preloadedQuery.environment) { + return source; + } else { + // if a call to loadQuery is made with a particular environment, and that + // preloaded query is passed to usePreloadedQuery in a different environmental + // context, we cannot re-use the existing preloaded query. Instead, we must + // re-execute the query with the new environment (at render time.) + // TODO T68036756 track occurences of this warning and turn it into a hard error + warning( + false, + 'usePreloadedQuery(): usePreloadedQuery was passed a preloaded query ' + + 'that was created with a different environment than the one that is currently ' + + 'in context. In the future, this will become a hard error.', + ); + return environment.execute({operation}); + } + }, + ); useLazyLoadQueryNodeParams = { componentDisplayName: 'usePreloadedQuery()',