[react-relay] Improve nullability handling of pagination hooks (#40579)

* Fix nullabily of usePaginationFragment to match that of useFragment

* usePaginationFragmanet: add test for the non-null case

* Fix nullabily of useBlockingPaginationFragment to match that of usePaginationFragment
This commit is contained in:
Martin Andert
2019-11-22 16:45:23 +01:00
committed by Sheetal Nandi
parent 4d7955b2e9
commit eaf62c8120
3 changed files with 286 additions and 18 deletions

View File

@@ -14,23 +14,30 @@ export interface ReturnType<TQuery extends OperationType, TKey, TFragmentData> {
export type $Call<Fn extends (...args: any[]) => any> = Fn extends (arg: any) => infer RT ? RT : never;
export type NonNullableReturnType<T extends { readonly ' $data'?: unknown }> = (arg: T) => NonNullable<T[' $data']>;
export type NullableReturnType<T extends { readonly ' $data'?: unknown | null }> = (arg: T) => T[' $data'] | null;
interface KeyType {
readonly ' $data'?: unknown;
}
type KeyReturnType<T extends KeyType> = (arg: T) => NonNullable<T[' $data']>;
export function useBlockingPaginationFragment<
TQuery extends OperationType,
TKey extends { readonly ' $data'?: unknown | null }
TKey extends KeyType
>(
fragmentInput: GraphQLTaggedNode,
parentFragmentRef: TKey,
componentDisplayName?: string,
): ReturnType<
// tslint:disable-next-line:no-unnecessary-generics
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<NonNullableReturnType<TKey> & NullableReturnType<TKey>>
>;
): // tslint:disable-next-line no-unnecessary-generics
ReturnType<TQuery, TKey, $Call<KeyReturnType<TKey>>>;
export function useBlockingPaginationFragment<
TQuery extends OperationType,
TKey extends KeyType
>(
fragmentInput: GraphQLTaggedNode,
parentFragmentRef: TKey | null,
componentDisplayName?: string,
): // tslint:disable-next-line no-unnecessary-generics
ReturnType<TQuery, TKey | null, $Call<KeyReturnType<TKey>> | null>;
export { };

View File

@@ -15,14 +15,28 @@ export interface ReturnType<TQuery extends OperationType, TKey, TFragmentData> {
export type $Call<Fn extends (...args: any[]) => any> = Fn extends (arg: any) => infer RT ? RT : never;
export type NonNullableReturnType<T extends { readonly ' $data'?: unknown }> = (arg: T) => NonNullable<T[' $data']>;
export type NullableReturnType<T extends { readonly ' $data'?: unknown | null }> = (arg: T) => T[' $data'] | null;
interface KeyType {
readonly ' $data'?: unknown;
}
type KeyReturnType<T extends KeyType> = (arg: T) => NonNullable<T[' $data']>;
export function useLegacyPaginationFragment<
TQuery extends OperationType,
TKey extends { readonly ' $data'?: unknown | null }
TKey extends KeyType
>(
fragmentInput: GraphQLTaggedNode,
parentFragmentRef: TKey,
): // tslint:disable-next-line no-unnecessary-generics
ReturnType<TQuery, TKey, $Call<NonNullableReturnType<TKey> & NullableReturnType<TKey>>>;
ReturnType<TQuery, TKey, $Call<KeyReturnType<TKey>>>;
export function useLegacyPaginationFragment<
TQuery extends OperationType,
TKey extends KeyType
>(
fragmentInput: GraphQLTaggedNode,
parentFragmentRef: TKey | null,
): // tslint:disable-next-line no-unnecessary-generics
ReturnType<TQuery, TKey | null, $Call<KeyReturnType<TKey>> | null>;
export {};

View File

@@ -12,6 +12,7 @@ import {
useFragment,
useRefetchableFragment,
usePaginationFragment,
useBlockingPaginationFragment,
} from 'react-relay/hooks';
const source = new RecordSource();
@@ -431,7 +432,7 @@ function PaginationFragment() {
}
interface Props {
user: FriendsListComponent_user$key;
user: FriendsListComponent_user$key | null;
}
return function FriendsList(props: Props) {
@@ -476,3 +477,249 @@ function PaginationFragment() {
);
};
}
function PaginationFragment_WithNonNullUserProp() {
interface FriendsListPaginationQueryVariables {
count?: number;
cursor?: string;
id: string;
}
interface FriendsListPaginationQueryResponse {
readonly node: {
readonly ' $fragmentRefs': FragmentRefs<'FriendsListComponent_user'>;
};
}
interface FriendsListPaginationQuery {
readonly response: FriendsListPaginationQueryResponse;
readonly variables: FriendsListPaginationQueryVariables;
}
interface FriendsListComponent_user {
readonly name: string;
readonly friends: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly name: string;
readonly age: number;
};
}>;
};
readonly id: string;
readonly ' $refType': 'FriendsListComponent_user';
}
type FriendsListComponent_user$data = FriendsListComponent_user;
interface FriendsListComponent_user$key {
readonly ' $data'?: FriendsListComponent_user$data;
readonly ' $fragmentRefs': FragmentRefs<'FriendsListComponent_user'>;
}
interface Props {
user: FriendsListComponent_user$key;
}
return function FriendsList(props: Props) {
const {
data,
loadNext,
loadPrevious,
hasNext,
hasPrevious,
isLoadingNext,
isLoadingPrevious,
refetch, // For refetching connection
} = usePaginationFragment<FriendsListPaginationQuery, FriendsListComponent_user$key>(
graphql`
fragment FriendsListComponent_user on User @refetchable(queryName: "FriendsListPaginationQuery") {
name
friends(first: $count, after: $cursor) @connection(key: "FriendsList_user_friends") {
edges {
node {
name
age
}
}
}
}
`,
props.user,
);
return (
<>
<h1>Friends of {data.name}:</h1>
{data.friends.edges.map(({ node }) => (
<div>
{node.name} - {node.age}
</div>
))}
<button onClick={() => loadNext(10)}>Load more friends</button>
</>
);
};
}
/**
* Tests for useBlockingPaginationFragment
* see https://relay.dev/docs/en/experimental/api-reference#useblockingpaginationfragment
*/
function BlockingPaginationFragment() {
interface FriendsListPaginationQueryVariables {
count?: number;
cursor?: string;
id: string;
}
interface FriendsListPaginationQueryResponse {
readonly node: {
readonly ' $fragmentRefs': FragmentRefs<'FriendsListComponent_user'>;
};
}
interface FriendsListPaginationQuery {
readonly response: FriendsListPaginationQueryResponse;
readonly variables: FriendsListPaginationQueryVariables;
}
interface FriendsListComponent_user {
readonly name: string;
readonly friends: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly name: string;
readonly age: number;
};
}>;
};
readonly id: string;
readonly ' $refType': 'FriendsListComponent_user';
}
type FriendsListComponent_user$data = FriendsListComponent_user;
interface FriendsListComponent_user$key {
readonly ' $data'?: FriendsListComponent_user$data;
readonly ' $fragmentRefs': FragmentRefs<'FriendsListComponent_user'>;
}
interface Props {
user: FriendsListComponent_user$key | null;
}
return function FriendsList(props: Props) {
const {
data,
loadNext,
loadPrevious,
hasNext,
hasPrevious,
refetch, // For refetching connection
} = useBlockingPaginationFragment<FriendsListPaginationQuery, FriendsListComponent_user$key>(
graphql`
fragment FriendsListComponent_user on User @refetchable(queryName: "FriendsListPaginationQuery") {
name
friends(first: $count, after: $cursor) @connection(key: "FriendsList_user_friends") {
edges {
node {
name
age
}
}
}
}
`,
props.user,
);
return (
<>
<h1>Friends of {data!.name}:</h1>
{data!.friends.edges.map(({ node }) => (
<div>
{node.name} - {node.age}
</div>
))}
<button onClick={() => loadNext(10)}>Load more friends</button>
</>
);
};
}
function BlockingPaginationFragment_WithNonNullUserProp() {
interface FriendsListPaginationQueryVariables {
count?: number;
cursor?: string;
id: string;
}
interface FriendsListPaginationQueryResponse {
readonly node: {
readonly ' $fragmentRefs': FragmentRefs<'FriendsListComponent_user'>;
};
}
interface FriendsListPaginationQuery {
readonly response: FriendsListPaginationQueryResponse;
readonly variables: FriendsListPaginationQueryVariables;
}
interface FriendsListComponent_user {
readonly name: string;
readonly friends: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly name: string;
readonly age: number;
};
}>;
};
readonly id: string;
readonly ' $refType': 'FriendsListComponent_user';
}
type FriendsListComponent_user$data = FriendsListComponent_user;
interface FriendsListComponent_user$key {
readonly ' $data'?: FriendsListComponent_user$data;
readonly ' $fragmentRefs': FragmentRefs<'FriendsListComponent_user'>;
}
interface Props {
user: FriendsListComponent_user$key;
}
return function FriendsList(props: Props) {
const {
data,
loadNext,
loadPrevious,
hasNext,
hasPrevious,
refetch, // For refetching connection
} = useBlockingPaginationFragment<FriendsListPaginationQuery, FriendsListComponent_user$key>(
graphql`
fragment FriendsListComponent_user on User @refetchable(queryName: "FriendsListPaginationQuery") {
name
friends(first: $count, after: $cursor) @connection(key: "FriendsList_user_friends") {
edges {
node {
name
age
}
}
}
}
`,
props.user,
);
return (
<>
<h1>Friends of {data.name}:</h1>
{data.friends.edges.map(({ node }) => (
<div>
{node.name} - {node.age}
</div>
))}
<button onClick={() => loadNext(10)}>Load more friends</button>
</>
);
};
}