react-relay
Version:
A framework for building GraphQL-driven React applications.
137 lines (123 loc) • 4.36 kB
Flow
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall relay
*/
;
import type {
FetchPolicy,
GraphQLResponse,
Observable,
OperationDescriptor,
OperationType,
RenderPolicy,
} from 'relay-runtime';
const ProfilerContext = require('./ProfilerContext');
const {
getQueryCacheIdentifier,
getQueryResourceForEnvironment,
} = require('./QueryResource');
const useFetchTrackingRef = require('./useFetchTrackingRef');
const useFragmentInternal = require('./useFragmentInternal');
const useRelayEnvironment = require('./useRelayEnvironment');
const React = require('react');
const {useContext, useEffect, useState, useRef} = React;
hook useLazyLoadQueryNode<TQuery: OperationType>({
query,
componentDisplayName,
fetchObservable,
fetchPolicy,
fetchKey,
renderPolicy,
}: {
query: OperationDescriptor,
componentDisplayName: string,
fetchObservable: Observable<GraphQLResponse>,
fetchPolicy?: ?FetchPolicy,
fetchKey?: ?string | ?number,
renderPolicy?: ?RenderPolicy,
}): TQuery['response'] {
const environment = useRelayEnvironment();
const profilerContext = useContext(ProfilerContext);
const QueryResource = getQueryResourceForEnvironment(environment);
const [forceUpdateKey, forceUpdate] = useState(0);
const {startFetch, completeFetch} = useFetchTrackingRef();
const cacheBreaker = `${forceUpdateKey}-${fetchKey ?? ''}`;
const cacheIdentifier = getQueryCacheIdentifier(
environment,
query,
fetchPolicy,
renderPolicy,
cacheBreaker,
);
const preparedQueryResult = profilerContext.wrapPrepareQueryResource(() => {
return QueryResource.prepareWithIdentifier(
cacheIdentifier,
query,
fetchObservable,
fetchPolicy,
renderPolicy,
{start: startFetch, complete: completeFetch, error: completeFetch},
profilerContext,
);
});
const maybeHiddenOrFastRefresh = useRef(false);
useEffect(() => {
return () => {
// Attempt to detect if the component was
// hidden (by Offscreen API), or fast refresh occured;
// Only in these situations would the effect cleanup
// for "unmounting" run multiple times, so if
// we are ever able to read this ref with a value
// of true, it means that one of these cases
// has happened.
maybeHiddenOrFastRefresh.current = true;
};
}, []);
useEffect(() => {
if (maybeHiddenOrFastRefresh.current === true) {
// This block only runs if the component has previously "unmounted"
// due to it being hidden by the Offscreen API, or during fast refresh.
// At this point, the current cached resource will have been disposed
// by the previous cleanup, so instead of attempting to
// do our regular commit setup, which would incorrectly attempt to
// retain a cached query resource that was disposed, we need to force
// a re-render so that the cache entry for this query is re-intiliazed and
// and re-evaluated (and potentially cause a refetch).
maybeHiddenOrFastRefresh.current = false;
forceUpdate(n => n + 1);
return;
}
const disposable = QueryResource.retain(
preparedQueryResult,
profilerContext,
);
return () => {
disposable.dispose();
};
// NOTE: We disable react-hooks-deps warning because the `environment`
// and `cacheIdentifier` identities are capturing all information about whether
// the effect should be re-executed and the query re-retained.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [environment, cacheIdentifier]);
useEffect(() => {
// Release any temporary retain that's not released. At this point, if the
// cacheIdentifier doesn't change, the query is still permanently retained,
// and the temporary retain is redundant.
QueryResource.releaseTemporaryRetain(preparedQueryResult);
// This effect is intended to run on every commit, thus no dependency
});
const {fragmentNode, fragmentRef} = preparedQueryResult;
const data = useFragmentInternal(
fragmentNode,
fragmentRef,
componentDisplayName,
);
return data;
}
module.exports = useLazyLoadQueryNode;