Skip to content

Commit

Permalink
update useBlockingPaginationFragment to use the new flow-types
Browse files Browse the repository at this point in the history
Reviewed By: kassens

Differential Revision: D42511067

fbshipit-source-id: 2977ab824a6b16865c379bcfa6d157bf77b170c2
  • Loading branch information
alunyov authored and facebook-github-bot committed Jan 17, 2023
1 parent d09f916 commit ab9f04a
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 83 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,80 +14,83 @@ import type {
FetchFn,
NonNullableData,
NullableData,
QueryOperation,
QueryVariables,
QueryVariablesSubset,
} from './utils';
import type {IEnvironment, OperationType} from 'relay-runtime';
import type {IEnvironment, Variables, OperationType} from 'relay-runtime';

import useBlockingPaginationFragment from '../useBlockingPaginationFragment';
import {
fragmentData,
fragmentInput,
refetchableFragmentInput,
keyAnotherNonNullable,
keyAnotherNullable,
keyNonNullable,
keyNullable,
} from './utils';

type ExpectedReturnType<
TQuery: OperationType,
TQueryVariables,
TRefetchVariables: Variables,
TLoadMoreVariables: Variables,
TFragmentData,
TQuery: OperationType = {
variables: TLoadMoreVariables,
response: TFragmentData,
},
> = {
data: TFragmentData,
loadNext: LoadMoreFn<TQuery>,
loadPrevious: LoadMoreFn<TQuery>,
hasNext: boolean,
hasPrevious: boolean,
refetch: FetchFn<TQueryVariables>,
refetch: FetchFn<TRefetchVariables>,
};

/* eslint-disable react-hooks/rules-of-hooks */

// Nullability of returned data type is correct
(useBlockingPaginationFragment<QueryOperation, _>(
fragmentInput,
(useBlockingPaginationFragment(
refetchableFragmentInput,
keyNonNullable,
): ExpectedReturnType<QueryOperation, QueryVariablesSubset, NonNullableData>);
): ExpectedReturnType<QueryVariablesSubset, QueryVariables, NonNullableData>);

(useBlockingPaginationFragment<QueryOperation, _>(
fragmentInput,
(useBlockingPaginationFragment(
refetchableFragmentInput,
keyNullable,
): ExpectedReturnType<QueryOperation, QueryVariables, NullableData>);
): ExpectedReturnType<QueryVariables, QueryVariables, NullableData>);

// $FlowExpectedError: can't cast nullable to non-nullable
(useBlockingPaginationFragment<QueryOperation, _>(
fragmentInput,
(useBlockingPaginationFragment(
refetchableFragmentInput,
keyNullable,
): ExpectedReturnType<QueryOperation, QueryVariables, NonNullableData>);
): ExpectedReturnType<QueryVariables, QueryVariables, NonNullableData>);

// $FlowExpectedError: actual type of returned data is correct
(useBlockingPaginationFragment<QueryOperation, _>(
fragmentInput,
(useBlockingPaginationFragment(
refetchableFragmentInput,
keyAnotherNonNullable,
): ExpectedReturnType<QueryOperation, QueryVariablesSubset, NonNullableData>);
): ExpectedReturnType<QueryVariables, QueryVariablesSubset, NonNullableData>);
// $FlowExpectedError
(useBlockingPaginationFragment<QueryOperation, _>(
fragmentInput,
(useBlockingPaginationFragment(
refetchableFragmentInput,
keyAnotherNullable,
): ExpectedReturnType<QueryOperation, QueryVariables, NullableData>);
): ExpectedReturnType<QueryVariables, QueryVariables, NullableData>);

// $FlowExpectedError: Key should not be a user provided object
useBlockingPaginationFragment<QueryOperation, _>(fragmentInput, {abc: 123});
useBlockingPaginationFragment(fragmentInput, {abc: 123});

// $FlowExpectedError: Key should not be an empty object
useBlockingPaginationFragment<QueryOperation, _>(fragmentInput, {});
useBlockingPaginationFragment(fragmentInput, {});

// $FlowExpectedError: Key should be the `<name>$key` type from generated flow
useBlockingPaginationFragment<QueryOperation, _>(fragmentInput, fragmentData);
useBlockingPaginationFragment(fragmentInput, fragmentData);

// Refetch function options:
declare var variables: QueryVariables;
declare var environment: IEnvironment;

const {refetch} = useBlockingPaginationFragment<QueryOperation, _>(
fragmentInput,
const {refetch} = useBlockingPaginationFragment(
refetchableFragmentInput,
keyNonNullable,
);
// $FlowExpectedError: internal option
Expand All @@ -104,8 +107,8 @@ refetch(variables, {
declare var extraVariables: {nickname: string};
declare var invalidVariables: {foo: string};

const {loadNext} = useBlockingPaginationFragment<QueryOperation, _>(
fragmentInput,
const {loadNext} = useBlockingPaginationFragment(
refetchableFragmentInput,
keyNonNullable,
);
// Accepts extraVariables
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ describe('useBlockingPaginationFragment', () => {
fragmentRef: mixed,
) {
// $FlowFixMe[incompatible-call]
const {data, ...result} = useBlockingPaginationFragmentOriginal<any, mixed>(
const {data, ...result} = useBlockingPaginationFragmentOriginal(
fragmentNode,
// $FlowFixMe[incompatible-call]
// $FlowFixMe[prop-missing]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ describe('useBlockingPaginationFragment with useTransition', () => {
) {
const [isPendingNext, startTransition] = useTransition();
// $FlowFixMe[incompatible-call]
const {data, ...result} = useBlockingPaginationFragmentOriginal<any, any>(
const {data, ...result} = useBlockingPaginationFragmentOriginal(
fragmentNode,
// $FlowFixMe[prop-missing]
// $FlowFixMe[incompatible-call]
Expand Down
144 changes: 91 additions & 53 deletions packages/react-relay/relay-hooks/useBlockingPaginationFragment.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,15 @@

'use strict';

import type {VariablesOf} from 'relay-runtime/util/RelayRuntimeTypes';
import type {Options} from './useRefetchableFragmentNode';

import type {LoadMoreFn, UseLoadMoreFunctionArgs} from './useLoadMoreFunction';
import type {RefetchFnDynamic} from './useRefetchableFragmentNode';
import type {
FragmentType,
GraphQLResponse,
GraphQLTaggedNode,
Variables,
Observer,
OperationType,
Disposable,
} from 'relay-runtime';

const useLoadMoreFunction = require('./useLoadMoreFunction');
Expand All @@ -34,36 +32,62 @@ const {
getFragmentIdentifier,
getPaginationMetadata,
} = require('relay-runtime');
import type {RefetchableFragment} from '../../relay-runtime/util/RelayRuntimeTypes';

export type ReturnType<TQuery: OperationType, TKey, TFragmentData> = {
data: TFragmentData,
loadNext: LoadMoreFn<TQuery>,
loadPrevious: LoadMoreFn<TQuery>,
type RefetchVariables<TVariables, TKey> =
// NOTE: This $Call ensures that the type of the variables is either:
// - nullable if the provided ref type is non-nullable
// - non-nullable if the provided ref type is nullable, and the caller need to provide the full set of variables
// prettier-ignore
$Call<
& (<TFragmentType>( { +$fragmentSpreads: TFragmentType, ... }) => $Shape<TVariables>)
& (<TFragmentType>(?{ +$fragmentSpreads: TFragmentType, ... }) => TVariables),
TKey,
>;

type RefetchFnBase<TVars, TOptions> = (
vars: TVars,
options?: TOptions,
) => Disposable;

type RefetchFn<TVariables, TKey, TOptions = Options> = RefetchFnBase<
RefetchVariables<TVariables, TKey>,
TOptions,
>;

type ReturnType<TVariables, TData, TKey> = {
// NOTE: This $Call ensures that the type of the returned data is either:
// - nullable if the provided ref type is nullable
// - non-nullable if the provided ref type is non-nullable
// prettier-ignore
data: $Call<
& (<TFragmentType>( { +$fragmentSpreads: TFragmentType, ... }) => TData)
& (<TFragmentType>(?{ +$fragmentSpreads: TFragmentType, ... }) => ?TData),
TKey,
>,
loadNext: LoadMoreFn<{
variables: TVariables,
response: TData,
}>,
loadPrevious: LoadMoreFn<{
variables: TVariables,
response: TData,
}>,
hasNext: boolean,
hasPrevious: boolean,
refetch: RefetchFnDynamic<TQuery, TKey>,
refetch: RefetchFn<TVariables, TKey>,
};

function useBlockingPaginationFragment<
TQuery: OperationType,
TKey: ?{+$data?: mixed, +$fragmentSpreads: FragmentType, ...},
TFragmentType: FragmentType,
TVariables: Variables,
TData,
TKey: ?{+$fragmentSpreads: TFragmentType, ...},
>(
fragmentInput: GraphQLTaggedNode,
fragmentInput: RefetchableFragment<TFragmentType, TData, TVariables>,
parentFragmentRef: TKey,
componentDisplayName: string = 'useBlockingPaginationFragment()',
): ReturnType<
TQuery,
TKey,
// NOTE: This $Call ensures that the type of the returned data is either:
// - nullable if the provided ref type is nullable
// - non-nullable if the provided ref type is non-nullable
// prettier-ignore
$Call<
& (<TFragmentData>( { +$data?: TFragmentData, ... }) => TFragmentData)
& (<TFragmentData>(?{ +$data?: TFragmentData, ... }) => ?TFragmentData),
TKey,
>,
> {
): ReturnType<TVariables, TData, TKey> {
const fragmentNode = getFragment(fragmentInput);
useStaticFragmentNodeWarning(
fragmentNode,
Expand All @@ -89,33 +113,39 @@ function useBlockingPaginationFragment<
refetch,
disableStoreUpdates,
enableStoreUpdates,
} = useRefetchableFragmentNode<TQuery, TKey>(
fragmentNode,
parentFragmentRef,
componentDisplayName,
);
} = useRefetchableFragmentNode<
{
response: TData,
variables: TVariables,
},
{
+$data: mixed,
...
},
>(fragmentNode, parentFragmentRef, componentDisplayName);
const fragmentIdentifier = getFragmentIdentifier(fragmentNode, fragmentRef);

// Backward pagination
const [loadPrevious, hasPrevious, disposeFetchPrevious] = useLoadMore<TQuery>(
{
componentDisplayName,
connectionPathInFragmentData,
direction: 'backward',
disableStoreUpdates,
enableStoreUpdates,
fragmentData,
fragmentIdentifier,
fragmentNode,
fragmentRef,
identifierField,
paginationMetadata,
paginationRequest,
},
);
const [loadPrevious, hasPrevious, disposeFetchPrevious] = useLoadMore<
TVariables,
TData,
>({
componentDisplayName,
connectionPathInFragmentData,
direction: 'backward',
disableStoreUpdates,
enableStoreUpdates,
fragmentData,
fragmentIdentifier,
fragmentNode,
fragmentRef,
identifierField,
paginationMetadata,
paginationRequest,
});

// Forward pagination
const [loadNext, hasNext, disposeFetchNext] = useLoadMore<TQuery>({
const [loadNext, hasNext, disposeFetchNext] = useLoadMore<TVariables, TData>({
componentDisplayName,
connectionPathInFragmentData,
direction: 'forward',
Expand All @@ -130,8 +160,8 @@ function useBlockingPaginationFragment<
paginationRequest,
});

const refetchPagination: RefetchFnDynamic<TQuery, TKey> = useCallback(
(variables: VariablesOf<TQuery>, options: void | Options) => {
const refetchPagination: RefetchFn<TVariables, TKey> = useCallback(
(variables: TVariables, options: void | Options) => {
disposeFetchNext();
disposeFetchPrevious();
return refetch(variables, {...options, __environment: undefined});
Expand All @@ -140,7 +170,8 @@ function useBlockingPaginationFragment<
);

return {
data: fragmentData,
// $FlowFixMe[incompatible-cast]
data: (fragmentData: TData),
loadNext,
loadPrevious,
hasNext,
Expand All @@ -149,7 +180,7 @@ function useBlockingPaginationFragment<
};
}

function useLoadMore<TQuery: OperationType>(args: {
function useLoadMore<TVariables: Variables, TData>(args: {
disableStoreUpdates: () => void,
enableStoreUpdates: () => void,
...$Exact<
Expand All @@ -162,7 +193,11 @@ function useLoadMore<TQuery: OperationType>(args: {
},
>,
>,
}): [LoadMoreFn<TQuery>, boolean, () => void] {
}): [
LoadMoreFn<{variables: TVariables, response: TData}>,
boolean,
() => void,
] {
const {disableStoreUpdates, enableStoreUpdates, ...loadMoreArgs} = args;
const [requestPromise, setRequestPromise] = useState<null | Promise<mixed>>(
null,
Expand Down Expand Up @@ -213,7 +248,10 @@ function useLoadMore<TQuery: OperationType>(args: {
// and blow away the whole list of items.
error: promiseResolve,
};
const [loadMore, hasMore, disposeFetch] = useLoadMoreFunction<TQuery>({
const [loadMore, hasMore, disposeFetch] = useLoadMoreFunction<{
variables: TVariables,
response: TData,
}>({
...loadMoreArgs,
observer,
onReset: handleReset,
Expand Down

0 comments on commit ab9f04a

Please sign in to comment.