[react-relay] add hook types (#39414)

* add relay-experimental types

* add tests to relay-experimental types

* update version on header
This commit is contained in:
Renan Machado
2019-10-28 07:56:40 -03:00
committed by Eloy Durán
parent dc156f32b2
commit 371a2eaabd
23 changed files with 1235 additions and 19 deletions

1
types/react-relay/hooks.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
export * from './lib/hooks';

View File

@@ -1,4 +1,4 @@
// Type definitions for react-relay 6.0
// Type definitions for react-relay 7.0
// Project: https://github.com/facebook/relay, https://facebook.github.io/relay
// Definitions by: Johannes Schickling <https://github.com/graphcool>
// Matt Martin <https://github.com/voxmatt>
@@ -8,8 +8,9 @@
// Kaare Hoff Skovgaard <https://github.com/kastermester>
// Matt Krick <https://github.com/mattkrick>
// Jared Kass <https://github.com/jdk243>
// Renan Machado <https://github.com/renanmav>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 3.0
// TypeScript Version: 3.3
import * as React from 'react';
import {
@@ -117,13 +118,17 @@ interface QueryRendererProps<TOperation extends OperationType> {
}) => React.ReactNode;
variables: TOperation['variables'];
}
declare class ReactRelayQueryRenderer<TOperation extends OperationType> extends React.Component<{
cacheConfig?: CacheConfig | null;
fetchPolicy?: FetchPolicy;
} & QueryRendererProps<TOperation>> {}
declare class ReactRelayQueryRenderer<TOperation extends OperationType> extends React.Component<
{
cacheConfig?: CacheConfig | null;
fetchPolicy?: FetchPolicy;
} & QueryRendererProps<TOperation>
> {}
export { ReactRelayQueryRenderer as QueryRenderer };
declare class ReactRelayLocalQueryRenderer<TOperation extends OperationType> extends React.Component<QueryRendererProps<TOperation>> {}
declare class ReactRelayLocalQueryRenderer<TOperation extends OperationType> extends React.Component<
QueryRendererProps<TOperation>
> {}
export { ReactRelayLocalQueryRenderer as LocalQueryRenderer };
export const ReactRelayContext: React.Context<RelayContext | null>;

17
types/react-relay/lib/hooks.d.ts vendored Normal file
View File

@@ -0,0 +1,17 @@
export { graphql } from 'relay-runtime';
export { fetchQuery } from './relay-experimental/fetchQuery';
export { EntryPointContainer } from './relay-experimental/EntryPointContainer';
export { LazyLoadEntryPointContainer } from './relay-experimental/LazyLoadEntryPointContainer';
export { RelayEnvironmentProvider } from './relay-experimental/RelayEnvironmentProvider';
export { preloadQuery } from './relay-experimental/preloadQuery';
export { prepareEntryPoint } from './relay-experimental/prepareEntryPoint';
export { useBlockingPaginationFragment } from './relay-experimental/useBlockingPaginationFragment';
export { useFragment } from './relay-experimental/useFragment';
export { useLazyLoadQuery } from './relay-experimental/useLazyLoadQuery';
export { useLegacyPaginationFragment as usePaginationFragment } from './relay-experimental/useLegacyPaginationFragment';
export { usePreloadedQuery } from './relay-experimental/usePreloadedQuery';
export { useRefetchableFragment } from './relay-experimental/useRefetchableFragment';
export { useRelayEnvironment } from './relay-experimental/useRelayEnvironment';

View File

@@ -0,0 +1,24 @@
/* tslint:disable:no-unnecessary-generics */
import { ElementType, ClassicElement } from 'react';
import { EntryPointComponent, PreloadedEntryPoint } from './EntryPointTypes';
export function EntryPointContainer<
TPreloadedQueries extends {},
TPreloadedNestedEntryPoints extends {},
TRuntimeProps extends {},
TExtraProps,
TEntryPointComponent extends EntryPointComponent<
TPreloadedQueries,
TPreloadedNestedEntryPoints,
TRuntimeProps,
TExtraProps
>
>({
entryPointReference,
props,
}: Readonly<{
entryPointReference: PreloadedEntryPoint<TEntryPointComponent>;
props: TRuntimeProps;
}>): ClassicElement<ElementType>;

View File

@@ -0,0 +1,152 @@
import {
OperationType,
ConcreteRequest,
RequestParameters,
IEnvironment,
Observable,
GraphQLResponse,
} from 'relay-runtime';
import { Component } from 'react';
export type PreloadFetchPolicy = 'store-or-network' | 'store-and-network' | 'network-only';
export interface PreloadOptions {
readonly fetchKey?: string | number;
readonly fetchPolicy?: PreloadFetchPolicy;
}
// Note: the phantom type parameter here helps ensures that the
// $Parameters.js value matches the type param provided to preloadQuery.
// tslint:disable-next-line interface-over-type-literal
export type PreloadableConcreteRequest<TQuery extends OperationType> = {};
export interface EnvironmentProviderOptions {
[key: string]: unknown;
}
export interface PreloadedQuery<
TQuery extends OperationType,
TEnvironmentProviderOptions = EnvironmentProviderOptions
> {
readonly environment: IEnvironment;
readonly environmentProviderOptions: TEnvironmentProviderOptions | null | undefined;
readonly fetchKey: string | number | null | undefined;
readonly fetchPolicy: PreloadFetchPolicy;
readonly name: string;
readonly source: Observable<GraphQLResponse> | null | undefined;
readonly variables: TQuery['variables'];
}
/**
* The Interface of the EntryPoints .entrypoint files
*
* Every .entrypoint file it's an object that must have two required fields:
* - getPreloadProps(...) function that will return the description of preloaded
* queries and preloaded (nested) entry points for the current entry point
* - root - JSResource of the module that will render those preloaded queries
*
* TEntryPointParams - object that contains all necessary information to execute
* the preloaders (routeParams, query variables)
*
* TPreloadedQueries - queries, defined in the root components
*
* TPreloadedEntryPoints - nested entry points, defined in the root components
*
* TRuntimeProps - the type of additional props that you may pass to the
* component (like `onClick` handler, etc) during runtime. Values for them
* defined during component runtime
*
* TExtraProps - a bag of extra props that you may define in `entrypoint` file
* and they will be passed to the EntryPointComponent as `extraProps`
*/
export type InternalEntryPointRepresentation<
TEntryPointParams,
TPreloadedQueries,
TPreloadedEntryPoints,
TRuntimeProps,
TExtraProps
> = Readonly<{
getPreloadProps: (
entryPointParams: TEntryPointParams,
) => PreloadProps<TEntryPointParams, TPreloadedQueries, TPreloadedEntryPoints, TExtraProps>;
root: unknown;
}>;
// The shape of the props of the entry point `root` component
export type EntryPointProps<
TPreloadedQueries,
TPreloadedEntryPoints = {},
TRuntimeProps = {},
TExtraProps = null
> = Readonly<{
entryPoints: TPreloadedEntryPoints;
extraProps: TExtraProps | null;
props: TRuntimeProps;
queries: TPreloadedQueries;
}>;
// Type of the entry point `root` component
export type EntryPointComponent<
TPreloadedQueries,
TPreloadedEntryPoints = {},
TRuntimeProps = {},
TExtraProps = null
> = Component<EntryPointProps<TPreloadedQueries, TPreloadedEntryPoints, TRuntimeProps, TExtraProps>>;
// Return type of the `getPreloadProps(...)` of the entry point
export type PreloadProps<
TPreloadParams,
TPreloadedQueries extends {},
TPreloadedEntryPoints extends {},
TExtraProps = null,
TEnvironmentProviderOptions = EnvironmentProviderOptions
> = Readonly<{
entryPoints?: { [T in keyof TPreloadedEntryPoints]: ExtractEntryPointTypeHelper<TPreloadParams> };
extraProps?: TExtraProps;
queries?: { [T in keyof TPreloadedQueries]: ExtractQueryTypeHelper<TEnvironmentProviderOptions> };
}>;
// Return type of the `prepareEntryPoint(...)` function
export type PreloadedEntryPoint<TEntryPointComponent> = Readonly<{
entryPoints: JSX.LibraryManagedAttributes<TEntryPointComponent, 'entryPoints'>;
extraProps: JSX.LibraryManagedAttributes<TEntryPointComponent, 'extraProps'>;
getComponent: () => TEntryPointComponent;
queries: JSX.LibraryManagedAttributes<TEntryPointComponent, 'queries'>;
}>;
export type ThinQueryParams<TQuery extends OperationType, TEnvironmentProviderOptions> = Readonly<{
environmentProviderOptions?: TEnvironmentProviderOptions | null;
options?: PreloadOptions | null;
parameters: PreloadableConcreteRequest<TQuery>;
variables: TQuery['variables'];
}>;
export type ThinNestedEntryPointParams<TEntryPointParams, TEntryPoint> = Readonly<{
entryPoint: TEntryPoint;
entryPointParams: TEntryPointParams;
}>;
export type ExtractQueryTypeHelper<TEnvironmentProviderOptions> = <TQuery extends OperationType>(
query: PreloadedQuery<TQuery>,
) => ThinQueryParams<TQuery, TEnvironmentProviderOptions>;
export type ExtractEntryPointTypeHelper<TEntryPointParams> = <TEntryPointComponent>(
entryPoint: PreloadedEntryPoint<TEntryPointComponent> | null | undefined,
) =>
| ThinNestedEntryPointParams<TEntryPointParams, EntryPoint<TEntryPointParams, TEntryPointComponent>>
| null
| undefined;
export type EntryPoint<TEntryPointParams, TEntryPointComponent> = InternalEntryPointRepresentation<
TEntryPointParams,
JSX.LibraryManagedAttributes<TEntryPointComponent, 'queries'>,
JSX.LibraryManagedAttributes<TEntryPointComponent, 'entryPoints'>,
JSX.LibraryManagedAttributes<TEntryPointComponent, 'props'>,
JSX.LibraryManagedAttributes<TEntryPointComponent, 'extraProps'>
>;
// tslint:disable-next-line interface-name
export interface IEnvironmentProvider<TOptions> {
getEnvironment(options: TOptions | null): IEnvironment;
}

View File

@@ -0,0 +1,46 @@
export interface Cache<T> {
get(key: string): T | null;
set(key: string, value: T): void;
has(key: string): boolean;
delete(key: string): void;
size(): number;
capacity(): number;
clear(): void;
}
/**
* JS maps (both plain objects and Map) maintain key insertion
* order, which means there is an easy way to simulate LRU behavior
* that should also perform quite well:
*
* To insert a new value, first delete the key from the inner _map,
* then _map.set(k, v). By deleting and reinserting, you ensure that the
* map sees the key as the last inserted key.
*
* Get does the same: if the key is present, delete and reinsert it.
*/
declare class LRUCache<T> implements Cache<T> {
_capacity: number;
_map: Map<string, T>;
constructor(capacity: number);
set(key: string, value: T): void;
get(key: string): T | null;
has(key: string): boolean;
delete(key: string): void;
size(): number;
capacity(): number;
clear(): void;
}
// tslint:disable-next-line:no-unnecessary-generics
declare function create<T>(capacity: number): LRUCache<T>;
export { create };

View File

@@ -0,0 +1,43 @@
import { EntryPoint, EntryPointComponent, EnvironmentProviderOptions, IEnvironmentProvider } from './EntryPointTypes';
export type EntryPointContainerProps<
TEntryPointParams,
TPreloadedQueries,
TPreloadedEntryPoints,
TRuntimeProps,
TExtraProps
> = Readonly<
Readonly<{
entryPoint: EntryPoint<
TEntryPointParams,
EntryPointComponent<TPreloadedQueries, TPreloadedEntryPoints, TRuntimeProps, TExtraProps>
>;
entryPointParams: TEntryPointParams;
environmentProvider?: IEnvironmentProvider<EnvironmentProviderOptions>;
props: TRuntimeProps;
}>
>;
export function LazyLoadEntryPointContainer<
TEntryPointParams extends {},
TPreloadedQueries extends {},
TPreloadedEntryPoints extends {},
TRuntimeProps extends {},
TExtraProps
>({
entryPoint,
entryPointParams,
props,
environmentProvider,
}: EntryPointContainerProps<
// tslint:disable-next-line no-unnecessary-generics
TEntryPointParams,
// tslint:disable-next-line no-unnecessary-generics
TPreloadedQueries,
// tslint:disable-next-line no-unnecessary-generics
TPreloadedEntryPoints,
// tslint:disable-next-line no-unnecessary-generics
TRuntimeProps,
// tslint:disable-next-line no-unnecessary-generics
TExtraProps
>): JSX.Element;

View File

@@ -0,0 +1,84 @@
import {
Disposable,
FragmentPointer,
GraphQLResponse,
IEnvironment,
Observable,
Observer,
OperationDescriptor,
ReaderFragment,
Snapshot,
} from 'relay-runtime';
import { Cache } from './LRUCache';
export type QueryResource = QueryResourceImpl;
export type FetchPolicy = 'store-only' | 'store-or-network' | 'store-and-network' | 'network-only';
export type RenderPolicy = 'full' | 'partial';
type QueryResourceCache = Cache<QueryResourceCacheEntry>;
interface QueryResourceCacheEntry {
readonly cacheKey: string;
getRetainCount(): number;
getValue(): Error | Promise<void> | QueryResult;
setValue(value: Error | Promise<void> | QueryResult): void;
temporaryRetain(environment: IEnvironment): void;
permanentRetain(environment: IEnvironment): Disposable;
}
interface QueryResult {
cacheKey: string;
fragmentNode: ReaderFragment;
fragmentRef: FragmentPointer;
operation: OperationDescriptor;
}
declare function getQueryCacheKey(
operation: OperationDescriptor,
fetchPolicy: FetchPolicy,
renderPolicy: RenderPolicy,
): string;
declare function getQueryResult(operation: OperationDescriptor, cacheKey: string): QueryResult;
declare function createQueryResourceCacheEntry(
cacheKey: string,
operation: OperationDescriptor,
value: Error | Promise<void> | QueryResult,
onDispose: (entry: QueryResourceCacheEntry) => void,
): QueryResourceCacheEntry;
declare class QueryResourceImpl {
constructor(environment: IEnvironment);
/**
* This function should be called during a Component's render function,
* to either read an existing cached value for the query, or fetch the query
* and suspend.
*/
prepare(
operation: OperationDescriptor,
fetchObservable: Observable<GraphQLResponse>,
maybeFetchPolicy: FetchPolicy | null,
maybeRenderPolicy: RenderPolicy | null,
observer?: Observer<Snapshot>,
cacheKeyBuster?: string | number,
): QueryResult;
/**
* This function should be called during a Component's commit phase
* (e.g. inside useEffect), in order to retain the operation in the Relay store
* and transfer ownership of the operation to the component lifecycle.
*/
retain(queryResult: QueryResult): Disposable;
getCacheEntry(
operation: OperationDescriptor,
fetchPolicy: FetchPolicy,
maybeRenderPolicy?: RenderPolicy,
): QueryResourceCacheEntry | null;
}
declare function createQueryResource(environment: IEnvironment): QueryResource;
declare function getQueryResourceForEnvironment(environment: IEnvironment): QueryResourceImpl;
export { createQueryResource, getQueryResourceForEnvironment };

View File

@@ -0,0 +1,9 @@
import { ReactNode } from 'react';
import { IEnvironment } from 'relay-runtime';
export interface Props {
children: ReactNode;
environment: IEnvironment;
}
export function RelayEnvironmentProvider(props: Props): JSX.Element;

View File

@@ -0,0 +1,85 @@
import { CacheConfig, GraphQLTaggedNode, IEnvironment, Observable, OperationType } from 'relay-runtime';
/**
* Fetches the given query and variables on the provided environment,
* and de-dupes identical in-flight requests.
*
* Observing a request:
* ====================
* fetchQuery returns an Observable which you can call .subscribe()
* on. Subscribe optionally takes an Observer, which you can provide to
* observe network events:
*
* ```
* fetchQuery(environment, query, variables).subscribe({
* // Called when network requests starts
* start: (subsctiption) => {},
*
* // Called after a payload is received and written to the local store
* next: (payload) => {},
*
* // Called when network requests errors
* error: (error) => {},
*
* // Called when network requests fully completes
* complete: () => {},
*
* // Called when network request is unsubscribed
* unsubscribe: (subscription) => {},
* });
* ```
*
* Request Promise:
* ================
* The obervable can be converted to a Promise with .toPromise(), which will
* resolve to a snapshot of the query data when the first response is received
* from the server.
*
* ```
* fetchQuery(environment, query, variables).then((data) => {
* // ...
* });
* ```
*
* In-flight request de-duping:
* ============================
* By default, calling fetchQuery multiple times with the same
* environment, query and variables will not initiate a new request if a request
* for those same parameters is already in flight.
*
* A request is marked in-flight from the moment it starts until the moment it
* fully completes, regardless of error or successful completion.
*
* NOTE: If the request completes _synchronously_, calling fetchQuery
* a second time with the same arguments in the same tick will _NOT_ de-dupe
* the request given that it will no longer be in-flight.
*
*
* Data Retention:
* ===============
* This function will NOT retain query data, meaning that it is not guaranteed
* that the fetched data will remain in the Relay store after the request has
* completed.
* If you need to retain the query data outside of the network request,
* you need to use `environment.retain()`.
*
*
* Cancelling requests:
* ====================
* If the disposable returned by subscribe is called while the
* request is in-flight, the request will be cancelled.
*
* ```
* const disposable = fetchQuery(...).subscribe(...);
*
* // This will cancel the request if it is in-flight.
* disposable.dispose();
* ```
* NOTE: When using .toPromise(), the request cannot be cancelled.
*/
export function fetchQuery<TQuery extends OperationType>(
environment: IEnvironment,
query: GraphQLTaggedNode,
variables: TQuery['variables'],
options?: { networkCacheConfig?: CacheConfig },
): Observable<TQuery['response']>;

View File

@@ -0,0 +1,10 @@
import { GraphQLResponse, IEnvironment, OperationType, Subscription } from 'relay-runtime';
import { PreloadableConcreteRequest, PreloadedQuery, PreloadFetchPolicy, PreloadOptions } from './EntryPointTypes';
export function preloadQuery<TQuery extends OperationType, TEnvironmentProviderOptions = any>(
environment: IEnvironment,
preloadableRequest: PreloadableConcreteRequest<TQuery>,
variables: TQuery['variables'],
options?: PreloadOptions | null,
environmentProviderOptions?: TEnvironmentProviderOptions | null,
): PreloadedQuery<TQuery, TEnvironmentProviderOptions>;

View File

@@ -0,0 +1,32 @@
import {
EntryPoint,
EntryPointComponent,
EnvironmentProviderOptions,
IEnvironmentProvider,
PreloadedEntryPoint,
} from './EntryPointTypes';
export function prepareEntryPoint<
TEntryPointParams extends {},
TPreloadedQueries extends {},
TPreloadedEntryPoints extends {},
TRuntimeProps extends {},
TExtraProps,
TEntryPointComponent extends EntryPointComponent<
// tslint:disable-next-line no-unnecessary-generics
TPreloadedQueries,
// tslint:disable-next-line no-unnecessary-generics
TPreloadedEntryPoints,
// tslint:disable-next-line no-unnecessary-generics
TRuntimeProps,
// tslint:disable-next-line no-unnecessary-generics
TExtraProps
>,
// tslint:disable-next-line no-unnecessary-generics
TEntryPoint extends EntryPoint<TEntryPointParams, TEntryPointComponent>
>(
environmentProvider: IEnvironmentProvider<EnvironmentProviderOptions>,
// tslint:disable-next-line no-unnecessary-generics
entryPoint: TEntryPoint,
entryPointParams: TEntryPointParams,
): PreloadedEntryPoint<TEntryPointComponent>;

View File

@@ -0,0 +1,36 @@
import { GraphQLResponse, GraphQLTaggedNode, Observer, OperationType } from 'relay-runtime';
import { LoadMoreFn, UseLoadMoreFunctionArgs } from './useLoadMoreFunction';
import { RefetchFnDynamic } from './useRefetchableFragmentNode';
export interface ReturnType<TQuery extends OperationType, TKey, TFragmentData> {
data: TFragmentData;
loadNext: LoadMoreFn;
loadPrevious: LoadMoreFn;
hasNext: boolean;
hasPrevious: boolean;
refetch: RefetchFnDynamic<TQuery, TKey>;
}
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;
export function useBlockingPaginationFragment<
TQuery extends OperationType,
TKey extends { readonly ' $data'?: unknown | null }
>(
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>>
>;

View File

@@ -0,0 +1,41 @@
import { GraphQLTaggedNode } from 'relay-runtime';
// NOTE: These declares ensure that the type of the returned data is:
// - non-nullable if the provided ref type is non-nullable
// - nullable if the provided ref type is nullable
// - array of non-nullable if the privoided ref type is an array of
// non-nullable refs
// - array of nullable if the privoided ref type is an array of nullable refs
type $Call<Fn extends (...args: any[]) => any> = Fn extends (arg: any) => infer RT ? RT : never;
type NonNullableReturnType<T extends { readonly ' $data'?: unknown }> = (arg: T) => NonNullable<T[' $data']>;
type NullableReturnType<T extends { readonly ' $data'?: unknown | null }> = (arg: T) => T[' $data'] | null;
type NonNullableArrayReturnType<T extends ReadonlyArray<{ readonly ' $data'?: unknown }>> = (arg: {
readonly ' $data': T;
}) => Array<NonNullable<T[0][' $data']>>;
type NullableArrayReturnType<T extends ReadonlyArray<{ readonly ' $data'?: unknown | null }>> = (
arg: T,
) => Array<T[0][' $data']> | null;
export function useFragment<TKey extends { readonly ' $data'?: unknown }>(
fragmentInput: GraphQLTaggedNode,
fragmentRef: TKey,
): $Call<NonNullableReturnType<TKey>>;
export function useFragment<TKey extends { readonly ' $data'?: unknown | null }>(
fragmentInput: GraphQLTaggedNode,
fragmentRef: TKey,
): $Call<NullableReturnType<TKey>>;
export function useFragment<TKey extends ReadonlyArray<{ readonly ' $data'?: unknown }>>(
fragmentInput: GraphQLTaggedNode,
fragmentRef: TKey,
): $Call<NonNullableArrayReturnType<TKey>>;
export function useFragment<TKey extends ReadonlyArray<{ readonly ' $data'?: unknown | null }>>(
fragmentInput: GraphQLTaggedNode,
fragmentRef: TKey,
): $Call<NullableArrayReturnType<TKey>>;
export {};

View File

@@ -0,0 +1,13 @@
import { FetchPolicy, RenderPolicy } from './QueryResource';
import { CacheConfig, GraphQLTaggedNode, OperationType } from 'relay-runtime';
export function useLazyLoadQuery<TQuery extends OperationType>(
gqlQuery: GraphQLTaggedNode,
variables: TQuery['variables'],
options?: {
fetchKey?: string | number;
fetchPolicy?: FetchPolicy;
networkCacheConfig?: CacheConfig;
renderPolicy_UNSTABLE?: RenderPolicy;
},
): TQuery['response'];

View File

@@ -0,0 +1,28 @@
import { LoadMoreFn, UseLoadMoreFunctionArgs } from './useLoadMoreFunction';
import { RefetchFnDynamic } from './useRefetchableFragmentNode';
import { GraphQLResponse, GraphQLTaggedNode, Observer, OperationType } from 'relay-runtime';
export interface ReturnType<TQuery extends OperationType, TKey, TFragmentData> {
data: TFragmentData;
loadNext: LoadMoreFn;
loadPrevious: LoadMoreFn;
hasNext: boolean;
hasPrevious: boolean;
isLoadingNext: boolean;
isLoadingPrevious: boolean;
refetch: RefetchFnDynamic<TQuery, TKey>;
}
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;
export function useLegacyPaginationFragment<
TQuery extends OperationType,
TKey extends { readonly ' $data'?: unknown | null }
>(
fragmentInput: GraphQLTaggedNode,
parentFragmentRef: TKey,
): // tslint:disable-next-line no-unnecessary-generics
ReturnType<TQuery, TKey, $Call<NonNullableReturnType<TKey> & NullableReturnType<TKey>>>;

View File

@@ -0,0 +1,45 @@
import {
ConcreteRequest,
Disposable,
GraphQLResponse,
Observer,
ReaderFragment,
ReaderPaginationMetadata,
RequestDescriptor,
} from 'relay-runtime';
export type Direction = 'forward' | 'backward';
export type LoadMoreFn = (
count: number,
options?: {
onComplete?: (arg: Error | null) => void;
},
) => Disposable;
export interface UseLoadMoreFunctionArgs {
direction: Direction;
fragmentNode: ReaderFragment;
fragmentIdentifier: string;
fragmentOwner: RequestDescriptor | ReadonlyArray<RequestDescriptor | null> | null;
fragmentData: unknown;
connectionPathInFragmentData: ReadonlyArray<string | number>;
fragmentRefPathInResponse: ReadonlyArray<string | number>;
paginationRequest: ConcreteRequest;
paginationMetadata: ReaderPaginationMetadata;
componentDisplayName: string;
observer: Observer<GraphQLResponse>;
onReset: () => void;
}
export function useLoadMoreFunction(args: UseLoadMoreFunctionArgs): [LoadMoreFn, boolean, () => void];
export function getConnectionState(
direction: Direction,
fragmentNode: ReaderFragment,
fragmentData: unknown,
connectionPathInFragmentData: ReadonlyArray<string | number>,
): {
cursor: string | null;
hasMore: boolean;
};

View File

@@ -0,0 +1,8 @@
import { GraphQLTaggedNode, OperationType } from 'relay-runtime';
import { PreloadedQuery } from './EntryPointTypes';
export function usePreloadedQuery<TQuery extends OperationType>(
gqlQuery: GraphQLTaggedNode,
preloadedQuery: PreloadedQuery<TQuery>,
): TQuery['response'];

View File

@@ -0,0 +1,18 @@
import { RefetchFnDynamic } from './useRefetchableFragmentNode';
import { GraphQLTaggedNode, OperationType } from 'relay-runtime';
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;
export type ReturnType<TQuery extends OperationType, TKey extends { readonly ' $data'?: unknown | null }> = [
$Call<NonNullableReturnType<TKey> & NullableReturnType<TKey>>,
RefetchFnDynamic<TQuery, TKey>,
];
export function useRefetchableFragment<
TQuery extends OperationType,
TKey extends { readonly ' $data'?: unknown | null }
// tslint:disable-next-line:no-unnecessary-generics
>(fragmentInput: GraphQLTaggedNode, fragmentRef: TKey): ReturnType<TQuery, TKey>;

View File

@@ -0,0 +1,124 @@
import { Disposable, OperationType, IEnvironment, Variables, ReaderFragment } from 'relay-runtime';
import { FetchPolicy, RenderPolicy } from './QueryResource';
export type RefetchFn<TQuery extends OperationType, TOptions = Options> = RefetchFnExact<TQuery, TOptions>;
export type RefetchFnDynamic<
TQuery extends OperationType,
TKey extends { readonly [key: string]: any } | null,
TOptions = Options
> = RefetchInexactDynamicResponse<TQuery, TOptions> & RefetchExactDynamicResponse<TQuery, TOptions>;
export type RefetchInexact<TQuery extends OperationType, TOptions> = (
data?: unknown,
) => RefetchFnInexact<TQuery, TOptions>;
export type RefetchInexactDynamicResponse<TQuery extends OperationType, TOptions> = ReturnType<
RefetchInexact<TQuery, TOptions>
>;
export type RefetchExact<TQuery extends OperationType, TOptions> = (
data?: unknown | null,
) => RefetchFnExact<TQuery, TOptions>;
export type RefetchExactDynamicResponse<TQuery extends OperationType, TOptions> = ReturnType<
RefetchExact<TQuery, TOptions>
>;
export type RefetchFnBase<TVars, TOptions> = (vars: TVars, options?: TOptions) => Disposable;
export type RefetchFnExact<TQuery extends OperationType, TOptions = Options> = RefetchFnBase<
TQuery['variables'],
TOptions
>;
export type RefetchFnInexact<TQuery extends OperationType, TOptions = Options> = RefetchFnBase<
TQuery['variables'],
TOptions
>;
export interface ReturnTypeNode<
TQuery extends OperationType,
TKey extends { readonly [key: string]: any } | null,
TOptions = Options
> {
fragmentData: unknown;
fragmentRef: unknown;
refetch: RefetchFnDynamic<TQuery, TKey, TOptions>;
disableStoreUpdates: () => void;
enableStoreUpdates: () => void;
}
export interface Options {
fetchPolicy?: FetchPolicy;
onComplete?: (arg: Error | null) => void;
}
export interface InternalOptions extends Options {
__environment?: IEnvironment;
renderPolicy?: RenderPolicy;
}
export type Action =
| {
type: string;
environment: IEnvironment;
fragmentIdentifier: string;
}
| {
type: string;
refetchVariables: Variables;
fetchPolicy?: FetchPolicy;
renderPolicy?: RenderPolicy;
onComplete?: (args: Error | null) => void;
environment: IEnvironment;
};
export interface RefetchState {
fetchPolicy: FetchPolicy | undefined;
renderPolicy: RenderPolicy | undefined;
mirroredEnvironment: IEnvironment;
mirroredFragmentIdentifier: string;
onComplete: ((arg: Error | null) => void) | undefined;
refetchEnvironment?: IEnvironment | null;
refetchVariables?: Variables | null;
}
export interface DebugIDandTypename {
id: string;
typename: string;
}
export function reducer(state: RefetchState, action: Action): RefetchState;
export function useRefetchableFragmentNode<
TQuery extends OperationType,
TKey extends { readonly [key: string]: any } | null
>(
fragmentNode: ReaderFragment,
parentFragmentRef: unknown,
componentDisplayName: string,
): // tslint:disable-next-line:no-unnecessary-generics
ReturnTypeNode<TQuery, TKey, InternalOptions>;
export function useRefetchFunction<TQuery extends OperationType>(
fragmentNode: any,
parentFragmentRef: any,
fragmentIdentifier: any,
fragmentRefPathInResponse: any,
fragmentData: any,
refetchGenerationRef: any,
dispatch: any,
disposeFetch: any,
componentDisplayName: any,
): // tslint:disable-next-line:no-unnecessary-generics
RefetchFn<TQuery, InternalOptions>;
export function readQuery(
environment: any,
query: any,
fetchPolicy: any,
renderPolicy: any,
refetchGeneration: any,
componentDisplayName: any,
{ start, complete }: any,
profilerContext: any,
): any;

View File

@@ -0,0 +1,3 @@
import { Environment } from 'relay-runtime';
export function useRelayEnvironment(): Environment;

View File

@@ -0,0 +1,400 @@
import * as React from 'react';
import { Environment, RecordSource, Store, Network, commitMutation, FragmentRefs } from 'relay-runtime';
import {
fetchQuery,
graphql,
RelayEnvironmentProvider,
useRelayEnvironment,
preloadQuery,
usePreloadedQuery,
useLazyLoadQuery,
useFragment,
useRefetchableFragment,
usePaginationFragment,
} from 'react-relay/hooks';
const source = new RecordSource();
const store = new Store(source);
function cacheHandler(operation: any, variables: { [key: string]: string }, cacheConfig: {}) {
return fetch('/graphql', {
method: 'POST',
body: JSON.stringify({
query: operation.text, // GraphQL text from input
variables,
}),
}).then((response: any) => {
return response.json();
});
}
const network = Network.create(cacheHandler);
const environment = new Environment({
network,
store,
});
const query = graphql`
query SomeQuery {
someType
}
`;
const variables = {};
/**
* Test of fetchQuery
*/
const dispose = fetchQuery(environment, query, variables).subscribe({
start: subsctiption => {},
next: payload => {},
error: (error: Error) => {},
complete: () => {},
unsubscribe: subscription => {},
});
dispose.unsubscribe();
interface AppQueryVariables {
id: string;
}
interface AppQueryResponse {
readonly user: {
readonly name: string;
} | null;
}
interface AppQuery {
readonly response: AppQueryResponse;
readonly variables: AppQueryVariables;
}
/**
* Test of RelayEnvironmentProvider
* see https://relay.dev/docs/en/experimental/api-reference#relayenvironmentprovider
*/
function Provider() {
return (
<RelayEnvironmentProvider environment={environment}>
<div />
</RelayEnvironmentProvider>
);
}
/**
* Test of useRelayEnvironment
* see https://relay.dev/docs/en/experimental/api-reference#userelayenvironment
*/
function RelayEnvironment() {
const environment = useRelayEnvironment();
const handler = React.useCallback(() => {
const mutation = graphql`
mutation AddTodoMutation($input: AddTodoInput!) {
addTodo(input: $input) {
todoEdge {
__typename
cursor
node {
complete
id
text
}
}
viewer {
id
totalCount
}
}
}
`;
commitMutation(environment, { mutation, variables: {} });
}, [environment]);
return (
<React.Suspense fallback={<div>Loading...</div>}>
<div />
</React.Suspense>
);
}
/**
* Tests of usePreloadedQuery
* see https://relay.dev/docs/en/experimental/api-reference#usepreloadedquery
*/
function PreloadedQuery() {
const appQuery = graphql`
query AppQuery($id: ID!) {
user(id: $id) {
name
}
}
`;
const result = preloadQuery<AppQuery>(environment, appQuery, { id: '4' }, { fetchPolicy: 'store-or-network' });
return function App() {
const data = usePreloadedQuery(query, result);
if (!data.user) return;
return <h1>{data.user.name}</h1>;
};
}
/**
* Tests for useLazyLoadQuery
* see https://relay.dev/docs/en/experimental/api-reference#uselazyloadquery
*/
function LazyLoadQuery() {
return function App() {
const data = useLazyLoadQuery<AppQuery>(
graphql`
query AppQuery($id: ID!) {
user(id: $id) {
name
}
}
`,
{ id: '4' },
{ fetchPolicy: 'store-and-network', networkCacheConfig: { force: true } },
);
return <h1>{data.user!.name}</h1>;
};
}
/**
* Tests for useFragment
* see https://relay.dev/docs/en/experimental/api-reference#usefragment
*/
function NonNullableFragment() {
interface UserComponent_user {
readonly id: string;
readonly name: string;
readonly profile_picture: {
readonly uri: string;
};
readonly ' $refType': 'UserComponent_user';
}
type UserComponent_user$data = UserComponent_user;
interface UserComponent_user$key {
readonly ' $data'?: UserComponent_user$data;
readonly ' $fragmentRefs': FragmentRefs<'UserComponent_user'>;
}
interface Props {
user: UserComponent_user$key;
}
return function UserComponent(props: Props) {
const data = useFragment(
graphql`
fragment UserComponent_user on User {
name
profile_picture(scale: 2) {
uri
}
}
`,
props.user,
);
return (
<>
<h1>{data.name}</h1>
<div>
<img src={data.profile_picture.uri} />
</div>
</>
);
};
}
function NullableFragment() {
interface UserComponent_user {
readonly id: string;
readonly name: string | null;
readonly profile_picture: {
readonly uri: string | null;
} | null;
readonly ' $refType': 'UserComponent_user';
}
type UserComponent_user$data = UserComponent_user;
interface UserComponent_user$key {
readonly ' $data'?: UserComponent_user$data;
readonly ' $fragmentRefs': FragmentRefs<'UserComponent_user'>;
}
interface Props {
user: UserComponent_user$key;
}
return function UserComponent(props: Props) {
const data = useFragment(
graphql`
fragment UserComponent_user on User {
name
profile_picture(scale: 2) {
uri
}
}
`,
props.user,
);
return (
<>
<h1>{data.name}</h1>
<div>
<img src={data.profile_picture!.uri || undefined} />
</div>
</>
);
};
}
/**
* Tests for useRefetchableFragment
* see https://relay.dev/docs/en/experimental/api-reference#userefetchablefragment
*/
function RefetchableFragment() {
interface CommentBodyRefetchQueryVariables {
lang?: string | null;
id?: string | null;
}
interface CommentBodyRefetchQueryResponse {
readonly node: {
readonly ' $fragmentRefs': FragmentRefs<'CommentBody_comment'>;
} | null;
}
interface CommentBodyRefetchQuery {
readonly response: CommentBodyRefetchQueryResponse;
readonly variables: CommentBodyRefetchQueryVariables;
}
interface CommentBody_comment {
readonly body: {
readonly text: string;
} | null;
readonly id: string | null;
readonly ' $refType': 'CommentBody_comment';
}
type CommentBody_comment$data = CommentBody_comment;
interface CommentBody_comment$key {
readonly ' $data'?: CommentBody_comment$data;
readonly ' $fragmentRefs': FragmentRefs<'CommentBody_comment'>;
}
interface Props {
comment: CommentBody_comment$key;
}
return function CommentBody(props: Props) {
const [data, refetch] = useRefetchableFragment<CommentBodyRefetchQuery, CommentBody_comment$key>(
graphql`
fragment CommentBody_comment on Comment @refetchable(queryName: "CommentBodyRefetchQuery") {
body(lang: $lang) {
text
}
}
`,
props.comment,
);
return (
<>
<p>{data!.body!.text}</p>
<button onClick={() => refetch({ lang: 'SPANISH' }, { fetchPolicy: 'store-or-network' })}>
Translate Comment
</button>
</>
);
};
}
/**
* Tests for usePaginationFragment
* see https://relay.dev/docs/en/experimental/api-reference#userefetchablefragment
*/
function PaginationFragment() {
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>
</>
);
};
}

View File

@@ -1,25 +1,17 @@
{
"compilerOptions": {
"module": "commonjs",
"lib": [
"es6",
"dom"
],
"lib": ["es6", "dom"],
"noImplicitAny": true,
"noImplicitThis": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"baseUrl": "../",
"typeRoots": [
"../"
],
"typeRoots": ["../"],
"types": [],
"noEmit": true,
"forceConsistentCasingInFileNames": true,
"jsx": "react"
},
"files": [
"index.d.ts",
"test/react-relay-tests.tsx"
]
}
"files": ["index.d.ts", "hooks.d.ts", "test/react-relay-tests.tsx", "test/relay-experimental-tests.tsx"]
}