Update types for relay-runtime 4.0 and react-relay 4.0 (#36212)

* Update types for relay-runtime 4.0 and react-relay 4.0

* [react-relay] Ensure correct relay prop is used

* [react-relay] Make relay prop optional for fragment containers

* [react-relay] Fix issue where props need to be in extended type

Also improves error when relay prop is omitted

* [react-relay] Add back componentRef prop

d3cfd58501/website/versioned_docs/version-classic/Modern-FragmentContainer.md (calling-component-instance-methods)

* Remove prettier comment that no longer applies
This commit is contained in:
Cameron Knight
2019-06-18 07:51:50 -07:00
committed by Eloy Durán
parent 4cb6b71197
commit 9bbf47034c
11 changed files with 1410 additions and 1623 deletions

View File

@@ -1,223 +0,0 @@
import * as React from 'react';
import * as RelayRuntimeTypes from 'relay-runtime';
// ~~~~~~~~~~~~~~~~~~~~~
// Maybe Fix
// ~~~~~~~~~~~~~~~~~~~~~
export type StoreReaderData = any;
export type StoreReaderOptions = any;
export type RelayStoreData = any;
export interface RelayQuery {
Fragment: any;
Node: any;
Root: any;
}
// ~~~~~~~~~~~~~~~~~~~~~
// Environment
// ~~~~~~~~~~~~~~~~~~~~~
export interface FragmentResolver {
dispose(): void;
resolve(
fragment: RelayQuery['Fragment'],
dataIDs: RelayRuntimeTypes.DataID | RelayRuntimeTypes.DataID[]
): StoreReaderData | StoreReaderData[] | undefined | null;
}
export interface RelayEnvironmentInterface {
forceFetch(
querySet: RelayRuntimeTypes.RelayQuerySet,
onReadyStateChange: RelayRuntimeTypes.ReadyStateChangeCallback
): RelayRuntimeTypes.Abortable;
getFragmentResolver(fragment: RelayQuery['Fragment'], onNext: () => void): FragmentResolver;
getStoreData(): RelayStoreData;
primeCache(
querySet: RelayRuntimeTypes.RelayQuerySet,
onReadyStateChange: RelayRuntimeTypes.ReadyStateChangeCallback
): RelayRuntimeTypes.Abortable;
read(
node: RelayQuery['Node'],
dataID: RelayRuntimeTypes.DataID,
options?: StoreReaderOptions
): StoreReaderData | void;
readQuery(root: RelayQuery['Root'], options?: StoreReaderOptions): StoreReaderData[] | void;
}
// ~~~~~~~~~~~~~~~~~~~~~
// the rest is to match pre-existing types from before v1
// ~~~~~~~~~~~~~~~~~~~~~
export type ClientMutationID = string;
/** Fragments are a hash of functions */
export interface Fragments {
[query: string]: (variables?: RelayVariables) => string;
}
export interface CreateContainerOpts {
initialVariables?: any;
fragments: Fragments;
prepareVariables?(prevVariables: RelayVariables): RelayVariables;
}
export interface RelayVariables {
[name: string]: any;
}
/** add static getFragment method to the component constructor */
export interface RelayContainerClass<T> extends React.ComponentClass<T> {
getFragment: (q: string, v?: RelayVariables) => string;
}
export interface RelayQueryRequestResolve {
response: any;
}
export type RelayMutationStatus =
| 'UNCOMMITTED' // Transaction hasn't yet been sent to the server. Transaction can be committed or rolled back.
| 'COMMIT_QUEUED' // Transaction was committed but another transaction with the same collision key is pending, so the transaction has been queued to send to the server.
| 'COLLISION_COMMIT_FAILED' // Transaction was queued for commit but another transaction with the same collision key failed. All transactions in the collision queue,
// including this one, have been failed. Transaction can be recommitted or rolled back.
| 'COMMITTING' // Transaction is waiting for the server to respond.
| 'COMMIT_FAILED';
export class RelayMutationTransaction {
applyOptimistic(): RelayMutationTransaction;
commit(): RelayMutationTransaction | null;
recommit(): void;
rollback(): void;
getError(): Error;
getStatus(): RelayMutationStatus;
getHash(): string;
getID(): ClientMutationID;
}
export interface RelayMutationRequest {
getQueryString(): string;
getVariables(): RelayVariables;
resolve(result: RelayQueryRequestResolve): any;
reject(errors: any): any;
}
export interface RelayQueryRequest {
resolve(result: RelayQueryRequestResolve): any;
reject(errors: any): any;
getQueryString(): string;
getVariables(): RelayVariables;
getID(): string;
getDebugName(): string;
}
export interface RelayNetworkLayer {
supports(...options: string[]): boolean;
}
export class DefaultNetworkLayer implements RelayNetworkLayer {
constructor(host: string, options?: any);
supports(...options: string[]): boolean;
}
export function createContainer<T>(
component: React.ComponentType<T>,
params?: CreateContainerOpts
): RelayContainerClass<T>;
export function injectNetworkLayer(networkLayer: RelayNetworkLayer): any;
export function isContainer(component: React.ComponentClass<any>): boolean;
export function QL(...args: any[]): string;
export class Route {
constructor(params?: RelayVariables);
}
/**
* Relay Mutation class, where T are the props it takes and S is the returned payload from Relay.Store.update.
* S is typically dynamic as it depends on the data the app is currently using, but it's possible to always
* return some data in the payload using REQUIRED_CHILDREN which is where specifying S is the most useful.
*/
export class Mutation<T, S> {
props: T;
constructor(props: T);
static getFragment(q: string): string;
}
export interface Transaction {
getError(): Error;
Status(): number;
}
export interface StoreUpdateCallbacks<T> {
onFailure?(transaction: Transaction): any;
onSuccess?(response: T): any;
}
export interface Store {
commitUpdate(mutation: Mutation<any, any>, callbacks?: StoreUpdateCallbacks<any>): any;
}
export const Store: Store;
export class RootContainer extends React.Component<RootContainerProps, any> {}
export interface RootContainerProps extends React.Props<RootContainer> {
Component: RelayContainerClass<any>;
route: Route;
renderLoading?(): JSX.Element;
renderFetched?(data: any): JSX.Element;
renderFailure?(error: Error, retry: (...args: any[]) => any): JSX.Element;
}
export class Renderer extends React.Component<RendererProps, any> {}
export interface RendererProps {
Container: RelayContainerClass<any>; // Relay container that defines fragments and the view to render.
forceFetch?: boolean; // Whether to send a server request regardless of data available on the client.
queryConfig: Route; // `QueryConfig` or `Relay.Route` that defines the query roots.
environment: Store; // An instance of `Relay.Environment` or any object that implements the `RelayEnvironment` interface.
render?: RenderCallback; // Called to render when data requirements are being fulfilled.
onReadyStateChange?: OnReadyStateChange;
}
export interface RenderStateConfig {
props?: { [propName: string]: any };
done: boolean;
error?: Error;
retry?(): void;
stale: boolean;
}
export type RenderCallback = (renderState: RenderStateConfig) => any;
export type ReadyStateEvent =
| 'ABORT'
| 'CACHE_RESTORED_REQUIRED'
| 'CACHE_RESTORE_FAILED'
| 'CACHE_RESTORE_START'
| 'NETWORK_QUERY_ERROR'
| 'NETWORK_QUERY_RECEIVED_ALL'
| 'NETWORK_QUERY_RECEIVED_REQUIRED'
| 'NETWORK_QUERY_START'
| 'STORE_FOUND_ALL'
| 'STORE_FOUND_REQUIRED';
export type OnReadyStateChange = (readyState: {
ready: boolean;
done: boolean;
stale: boolean;
error?: Error;
events: ReadyStateEvent[];
aborted: boolean;
}) => void;
export interface RelayProp<V = any> {
readonly route: { name: string }; // incomplete, also has params and queries
readonly variables: V;
readonly pendingVariables?: V;
setVariables(variables: V, onReadyStateChange?: OnReadyStateChange): void;
forceFetch(variables: V, onReadyStateChange?: OnReadyStateChange): void;
hasOptimisticUpdate(record: any): boolean;
getPendingTransactions(record: any): RelayMutationTransaction[];
commitUpdate(mutation: Mutation<any, any>, callbacks?: StoreUpdateCallbacks<any>): any;
}
export interface RelayProps<V> {
readonly relay: RelayProp<V>;
}

View File

@@ -1,85 +0,0 @@
export { QueryRenderer, fetchQuery, graphql } from './index';
import {
ConnectionConfig,
RelayPaginationProp as RelayModernPaginationProp,
RelayRefetchProp as RelayModernRefetchProp,
} from './index';
export { ConcreteFragment, ConcreteRequest, ConcreteBatchRequest } from 'relay-runtime';
import * as RelayRuntimeTypes from 'relay-runtime';
import { RelayEnvironmentInterface } from './classic';
// ~~~~~~~~~~~~~~~~~~~~~
// Maybe Fix
// ~~~~~~~~~~~~~~~~~~~~~
export type ConcreteFragmentDefinition = object;
export type ConcreteOperationDefinition = object;
// ~~~~~~~~~~~~~~~~~~~~~
// Util
// ~~~~~~~~~~~~~~~~~~~~~
export function getFragment(q: string, v?: RelayRuntimeTypes.Variables): string;
export interface ComponentWithFragment<T> extends React.ComponentClass<T> {
getFragment: typeof getFragment;
}
export interface StatelessWithFragment<T> extends React.StatelessComponent<T> {
getFragment: typeof getFragment;
}
export type ReactFragmentComponent<T> = ComponentWithFragment<T> | StatelessWithFragment<T>;
export type RelayClassicEnvironment = RelayEnvironmentInterface;
// ~~~~~~~~~~~~~~~~~~~~~
// RelayCompatTypes
// ~~~~~~~~~~~~~~~~~~~~~
export type CompatEnvironment = RelayRuntimeTypes.Environment | RelayClassicEnvironment;
// ~~~~~~~~~~~~~~~~~~~~~
// RelayProps
// ~~~~~~~~~~~~~~~~~~~~~
export interface RelayProp {
environment: CompatEnvironment;
}
export type RelayPaginationProp = RelayModernPaginationProp & RelayProp;
export type RelayRefetchProp = RelayModernRefetchProp & RelayProp;
// ~~~~~~~~~~~~~~~~~~~~~
// RelayCompatMutations
// ~~~~~~~~~~~~~~~~~~~~~
export function commitMutation(
environment: CompatEnvironment,
config: RelayRuntimeTypes.MutationConfig<any>
): RelayRuntimeTypes.Disposable;
export function applyOptimisticMutation(
environment: CompatEnvironment,
config: RelayRuntimeTypes.OptimisticMutationConfig
): RelayRuntimeTypes.Disposable;
// ~~~~~~~~~~~~~~~~~~~~~
// RelayCompatContainers
// ~~~~~~~~~~~~~~~~~~~~~
export interface GeneratedNodeMap {
[key: string]: RelayRuntimeTypes.GraphQLTaggedNode;
}
export function createFragmentContainer<T>(
Component: React.ComponentType<T>,
fragmentSpec: RelayRuntimeTypes.GraphQLTaggedNode | GeneratedNodeMap
): ReactFragmentComponent<T>;
export function createRefetchContainer<T>(
Component: React.ComponentType<T>,
fragmentSpec: RelayRuntimeTypes.GraphQLTaggedNode | GeneratedNodeMap,
taggedNode: RelayRuntimeTypes.GraphQLTaggedNode
): ReactFragmentComponent<T>;
export function createPaginationContainer<T>(
Component: React.ComponentType<T>,
fragmentSpec: RelayRuntimeTypes.GraphQLTaggedNode | GeneratedNodeMap,
connectionConfig: ConnectionConfig<T>
): ReactFragmentComponent<T>;
// ~~~~~~~~~~~~~~~~~~~~~
// injectDefaultVariablesProvider
// ~~~~~~~~~~~~~~~~~~~~~
export type VariablesProvider = () => RelayRuntimeTypes.Variables;
export function injectDefaultVariablesProvider(variablesProvider: VariablesProvider): void;

View File

@@ -1,4 +1,4 @@
// Type definitions for react-relay 1.3
// Type definitions for react-relay 4.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,26 +8,61 @@
// Kaare Hoff Skovgaard <https://github.com/kastermester>
// Matt Krick <https://github.com/mattkrick>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 2.9
// Prettified with:
// $ prettier --parser typescript --tab-width 4 --semi --trailing-comma es5 --write --print-width 120 \
// types/{react-relay,relay-runtime}/{,*}/*.ts*
export {
commitLocalUpdate,
commitRelayModernMutation as commitMutation,
fetchRelayModernQuery as fetchQuery,
GraphQLTaggedNode,
requestRelaySubscription as requestSubscription,
} from 'relay-runtime';
// TypeScript Version: 3.0
import * as React from 'react';
import * as RelayRuntimeTypes from 'relay-runtime';
import {
Environment,
Variables,
Disposable,
Observer,
CacheConfig,
GraphQLTaggedNode,
RelayContext,
PageInfo,
OperationType,
} from 'relay-runtime';
// ~~~~~~~~~~~~~~~~~~~~~
// Utility types
// ~~~~~~~~~~~~~~~~~~~~~
// ./ReactRelayTypes
export interface RelayProp {
environment: Environment;
refetch: undefined; // ensures no RelayRefetchProp is used with a fragment container
hasMore: undefined; // ensures no RelayPaginationProp is used with a fragment container
}
export interface RelayRefetchProp {
environment: Environment;
refetch: (
refetchVariables: Variables | ((fragmentVariables: Variables) => Variables),
renderVariables?: Variables | null,
observerOrCallback?: ObserverOrCallback | null,
options?: RefetchOptions
) => Disposable;
hasMore: undefined; // ensures no RelayPaginationProp is used with a refetch container
}
export interface RefetchOptions {
force?: boolean;
fetchPolicy?: 'store-or-network' | 'network-only';
}
type ObserverOrCallback = Observer<void> | ((error: Error | null | undefined) => void);
export interface RelayPaginationProp {
readonly environment: Environment;
readonly hasMore: () => boolean;
readonly isLoading: () => boolean;
readonly loadMore: (
pageSize: number,
observerOrCallback?: ObserverOrCallback | null,
options?: RefetchOptions | null
) => Disposable | null | undefined;
readonly refetchConnection: (
totalCount: number,
observerOrCallback?: ObserverOrCallback | null,
refetchVariables?: Variables | null
) => Disposable | null | undefined;
refetch: undefined; // ensures no RelayRefetchProp is used with a pagination container
}
export interface _RefType<T> {
' $refType': T;
@@ -42,160 +77,99 @@ export type FragmentOrRegularProp<T> = T extends _RefType<infer U>
? ReadonlyArray<_FragmentRefs<U>>
: T;
export type MappedFragmentProps<T> = { [K in keyof T]: FragmentOrRegularProp<T[K]> };
export type MappedFragmentProps<T> = {
[K in keyof T]: FragmentOrRegularProp<T[K]>;
};
export type RemoveRelayProp<P> = Pick<P, Exclude<keyof P, 'relay'>>;
export {
DataID,
DeclarativeMutationConfig,
Disposable,
Environment,
GraphQLTaggedNode,
MutationType,
NormalizationSelector,
OperationDescriptor,
RangeOperation,
ReaderSelector,
RelayContext,
Snapshot,
Variables,
MutationTypes,
RangeOperations,
applyOptimisticMutation,
commitLocalUpdate,
commitMutation,
fetchQuery,
graphql,
requestSubscription,
} from 'relay-runtime';
export interface ComponentRef {
componentRef?: (ref: any) => void;
export type DataFrom = 'NETWORK_ONLY' | 'STORE_THEN_NETWORK';
declare class ReactRelayQueryRenderer<TOperation extends OperationType> extends React.Component<{
cacheConfig?: CacheConfig | null;
dataFrom?: DataFrom;
environment: Environment;
query: GraphQLTaggedNode | null | undefined;
render: (renderProps: {
error: Error | null;
props: TOperation['response'] | null;
retry: (() => void) | null;
}) => React.ReactNode;
variables: TOperation['variables'];
}> {}
export { ReactRelayQueryRenderer as QueryRenderer };
export const ReactRelayContext: React.Context<RelayContext | null>;
interface GeneratedNodeMap {
[key: string]: GraphQLTaggedNode;
}
export type RelayContainer<P> = React.ComponentType<MappedFragmentProps<RemoveRelayProp<P>> & ComponentRef>;
export type ContainerProps<Props> = MappedFragmentProps<Pick<Props, Exclude<keyof Props, 'relay'>>>;
// ~~~~~~~~~~~~~~~~~~~~~
// Maybe Fix
// ~~~~~~~~~~~~~~~~~~~~~
export type ConcreteFragmentDefinition = object;
export type ConcreteOperationDefinition = object;
export type Container<Props> = React.ComponentType<ContainerProps<Props> & { componentRef?: (ref: any) => void }>;
// ~~~~~~~~~~~~~~~~~~~~~
// RelayProp
// ~~~~~~~~~~~~~~~~~~~~~
// note: refetch and pagination containers augment this
export interface RelayProp {
environment: RelayRuntimeTypes.Environment;
}
export function createFragmentContainer<Props>(
Component: React.ComponentType<Props & { relay?: RelayProp }>,
fragmentSpec: GraphQLTaggedNode | GeneratedNodeMap
): Container<Props>;
// ~~~~~~~~~~~~~~~~~~~~~
// RelayQL
// ~~~~~~~~~~~~~~~~~~~~~
export function RelayQL(strings: string[], ...substitutions: any[]): RelayRuntimeTypes.RelayConcreteNode;
// ~~~~~~~~~~~~~~~~~~~~~
// ReactRelayTypes
// ~~~~~~~~~~~~~~~~~~~~~
export interface GeneratedNodeMap {
[key: string]: RelayRuntimeTypes.GraphQLTaggedNode;
}
/**
* Runtime function to correspond to the `graphql` tagged template function.
* All calls to this function should be transformed by the plugin.
*/
export interface GraphqlInterface {
(strings: string[] | TemplateStringsArray): RelayRuntimeTypes.GraphQLTaggedNode;
experimental(strings: string[] | TemplateStringsArray): RelayRuntimeTypes.GraphQLTaggedNode;
}
export const graphql: GraphqlInterface;
// ~~~~~~~~~~~~~~~~~~~~~
// ReactRelayQueryRenderer
// ~~~~~~~~~~~~~~~~~~~~~
export interface QueryRendererProps<T extends RelayRuntimeTypes.OperationBase = RelayRuntimeTypes.OperationDefaults> {
cacheConfig?: RelayRuntimeTypes.CacheConfig;
dataFrom?: 'NETWORK_ONLY' | 'STORE_THEN_NETWORK';
environment: RelayRuntimeTypes.Environment;
query?: RelayRuntimeTypes.GraphQLTaggedNode | null;
render(readyState: ReadyState<T['response']>): React.ReactNode;
variables: T['variables'];
rerunParamExperimental?: RelayRuntimeTypes.RerunParam;
}
export interface ReadyState<T extends RelayRuntimeTypes.Variables = RelayRuntimeTypes.Variables> {
error: Error | undefined | null;
props: T | undefined | null;
retry?(): void;
}
export class ReactRelayQueryRenderer<T extends RelayRuntimeTypes.OperationBase> extends React.Component<
QueryRendererProps<T>
> {}
export class QueryRenderer<
T extends RelayRuntimeTypes.OperationBase = RelayRuntimeTypes.OperationDefaults
> extends ReactRelayQueryRenderer<T> {}
// ~~~~~~~~~~~~~~~~~~~~~
// createFragmentContainer
// ~~~~~~~~~~~~~~~~~~~~~
export function createFragmentContainer<P>(
Component: React.ComponentType<P>,
fragmentSpec: RelayRuntimeTypes.GraphQLTaggedNode | GeneratedNodeMap
): RelayContainer<P>;
// ~~~~~~~~~~~~~~~~~~~~~
// createPaginationContainer
// ~~~~~~~~~~~~~~~~~~~~~
export interface PageInfo {
endCursor: string | undefined | null;
hasNextPage: boolean;
hasPreviousPage: boolean;
startCursor: string | undefined | null;
}
export interface ConnectionData {
edges?: ReadonlyArray<any>;
interface ConnectionData {
edges?: ReadonlyArray<any> | null;
pageInfo?: Partial<PageInfo> | null;
}
export type RelayPaginationProp = RelayProp & {
hasMore(): boolean;
isLoading(): boolean;
loadMore(
pageSize: number,
callback?: ((error?: Error) => void) | null,
options?: RefetchOptions
): RelayRuntimeTypes.Disposable | undefined | null;
refetchConnection(
totalCount: number,
callback: (error?: Error) => void,
refetchVariables?: RelayRuntimeTypes.Variables
): RelayRuntimeTypes.Disposable | undefined | null;
};
export function FragmentVariablesGetter(
prevVars: RelayRuntimeTypes.Variables,
totalCount: number
): RelayRuntimeTypes.Variables;
export interface ConnectionConfig<P> {
type FragmentVariablesGetter = (prevVars: Variables, totalCount: number) => Variables;
export interface ConnectionConfig<Props = object> {
direction?: 'backward' | 'forward';
getConnectionFromProps?(props: P): ConnectionData | undefined | null;
getFragmentVariables?: typeof FragmentVariablesGetter;
getVariables(
props: { [propName: string]: any },
paginationInfo: { count: number; cursor?: string },
fragmentVariables: RelayRuntimeTypes.Variables
): RelayRuntimeTypes.Variables;
query: RelayRuntimeTypes.GraphQLTaggedNode;
getConnectionFromProps?: (props: Props) => ConnectionData | null | undefined;
getFragmentVariables?: FragmentVariablesGetter;
getVariables: (
props: Props,
paginationInfo: { count: number; cursor?: string | null },
fragmentVariables: Variables
) => Variables;
query: GraphQLTaggedNode;
}
export function createPaginationContainer<P>(
Component: React.ComponentType<P>,
fragmentSpec: RelayRuntimeTypes.GraphQLTaggedNode | GeneratedNodeMap,
connectionConfig: ConnectionConfig<P>
): RelayContainer<P>;
// ~~~~~~~~~~~~~~~~~~~~~
// createRefetchContainer
// ~~~~~~~~~~~~~~~~~~~~~
export interface RefetchOptions {
force?: boolean;
rerunParamExperimental?: RelayRuntimeTypes.RerunParam;
}
export type RelayRefetchProp = RelayProp & {
refetch(
refetchVariables:
| RelayRuntimeTypes.Variables
| ((fragmentVariables: RelayRuntimeTypes.Variables) => RelayRuntimeTypes.Variables),
renderVariables?: RelayRuntimeTypes.Variables,
callback?: (error?: Error) => void,
options?: RefetchOptions
): RelayRuntimeTypes.Disposable;
};
export function createRefetchContainer<P>(
Component: React.ComponentType<P>,
fragmentSpec: RelayRuntimeTypes.GraphQLTaggedNode | GeneratedNodeMap,
taggedNode: RelayRuntimeTypes.GraphQLTaggedNode
): RelayContainer<P>;
export function createPaginationContainer<Props>(
Component: React.ComponentType<
Props & {
relay: RelayPaginationProp;
}
>,
fragmentSpec: GraphQLTaggedNode | GeneratedNodeMap,
connectionConfig: ConnectionConfig<Props>
): Container<Props>;
// ~~~~~~~~~~~~~~~~~~~~~
// Context
// ~~~~~~~~~~~~~~~~~~~~~
export const ReactRelayContext: React.Context<RelayRuntimeTypes.CRelayContext<RelayRuntimeTypes.Environment>>;
export function createRefetchContainer<Props>(
Component: React.ComponentType<
Props & {
relay: RelayRefetchProp;
}
>,
fragmentSpec: GraphQLTaggedNode | GeneratedNodeMap,
taggedNode: GraphQLTaggedNode
): Container<Props>;

View File

@@ -1,92 +0,0 @@
import * as React from 'react';
import * as Relay from 'react-relay/classic';
import { CompatContainer } from './react-relay-compat-tests';
interface Props {
text: string;
userId: string;
}
export default class AddTweetMutation extends Relay.Mutation<Props, {}> {
getMutation() {
return Relay.QL`mutation{addTweet}`;
}
getFatQuery() {
return Relay.QL`
fragment on AddTweetPayload {
tweetEdge
user
}
`;
}
getConfigs() {
return [
{
type: 'RANGE_ADD',
parentName: 'user',
parentID: this.props.userId,
connectionName: 'tweets',
edgeName: 'tweetEdge',
rangeBehaviors: {
'': 'append',
},
},
];
}
getVariables() {
return this.props;
}
}
interface ArtwokRelayVariables {
artworkID: string;
}
interface ArtworkProps extends Relay.RelayProps<ArtwokRelayVariables> {
artwork: {
title: string;
};
}
class Artwork extends React.Component<ArtworkProps> {
render() {
return <a href={`/artworks/${this.props.relay.variables.artworkID}`}>{this.props.artwork.title}</a>;
}
}
const ArtworkContainer = Relay.createContainer(Artwork, {
fragments: {
artwork: () => Relay.QL`
fragment on Artwork {
title
${CompatContainer.getFragment('whatever')}
}
`,
},
});
class StubbedArtwork extends React.Component {
render() {
const props = {
artwork: { title: 'CHAMPAGNE FORMICA FLAG' },
relay: {
route: {
name: 'champagne',
},
variables: {
artworkID: 'champagne-formica-flag',
},
setVariables: () => {},
forceFetch: () => {},
hasOptimisticUpdate: () => false,
getPendingTransactions: (): any => undefined,
commitUpdate: () => {},
},
};
return <ArtworkContainer {...props} />;
}
}

View File

@@ -1,53 +0,0 @@
import * as React from 'react';
import {
QueryRenderer as CompatQueryRenderer,
createFragmentContainer as createFragmentContainerCompat,
commitMutation as commitMutationCompat,
CompatEnvironment,
RelayPaginationProp as RelayPaginationPropCompat,
} from 'react-relay/compat';
import { configs, mutation, optimisticResponse } from './react-relay-tests';
// testting compat mutation with classic environment
function markNotificationAsReadCompat(environment: CompatEnvironment, source: string, storyID: string) {
const variables = {
input: {
source,
storyID,
},
};
commitMutationCompat(environment, {
configs,
mutation,
optimisticResponse,
variables,
onCompleted: (response, errors) => {
console.log('Response received from server.');
},
onError: err => console.error(err),
updater: (store, data) => {
const field = store.get(storyID);
if (field) {
field.setValue(data.story, 'story');
}
},
});
}
interface CompatProps {
relay: RelayPaginationPropCompat;
}
class CompatComponent extends React.Component<CompatProps> {
markNotificationAsRead(source: string, storyID: string) {
markNotificationAsReadCompat(this.props.relay.environment, source, storyID);
}
render() {
return <div />;
}
}
export const CompatContainer = createFragmentContainerCompat(CompatComponent, {});

View File

@@ -1,4 +1,5 @@
// tslint:disable:interface-over-type-literal
// tslint:disable:use-default-type-parameter
import * as React from 'react';
import { Environment, Network, RecordSource, Store, ConnectionHandler } from 'relay-runtime';
@@ -21,7 +22,7 @@ import {
// Modern Environment
// ~~~~~~~~~~~~~~~~~~~~~
function fetchQuery(operation: any, variables: any, cacheConfig: {}) {
return fetch('/graphql');
return fetch('/graphql').then(response => response.json());
}
const network = Network.create(fetchQuery);
const source = new RecordSource();
@@ -76,7 +77,7 @@ const MyEmptyQueryRenderer = () => (
<QueryRenderer
environment={modernEnvironment}
// NOTE: let's intentionally leave out `query`
// query={undefined}
query={undefined}
variables={{}}
render={({ error, props }) => {
if (error) {
@@ -161,7 +162,7 @@ const Story = (() => {
}
`,
},
graphql.experimental`
graphql`
query StoryRefetchQuery($id: ID!) {
story(id: $id) {
...Story_story
@@ -170,12 +171,58 @@ const Story = (() => {
`
);
function doesNotRequireRelayPropToBeProvided() {
function requiresTheRightProps() {
const onLike = (id: string) => console.log(`Liked story #${id}`);
const story: { ' $fragmentRefs': Story_story$ref } = {} as any;
<StoryRefetchContainer story={story} onLike={onLike} />;
}
function requiresTheCorrectFragmentRef() {
const onLike = (id: string) => console.log(`Liked story #${id}`);
const feed: { ' $fragmentRefs': FeedStories_feed$ref } = {} as any;
// $ExpectError
<StoryRefetchContainer story={feed} onLike={onLike} />;
}
function doesNotRequireRelayPropToBeProvidedByParent() {
const onLike = (id: string) => console.log(`Liked story #${id}`);
const story: { ' $fragmentRefs': Story_story$ref } = {} as any;
const relayProp: RelayRefetchProp = {} as any;
// $ExpectError
<StoryRefetchContainer story={story} onLike={onLike} relay={relayProp} />;
}
function requiresTheRelayPropInPropsInterface() {
// FIXME: This should throw a type error, but doesn't
// const FunctionComponent: React.FC<{}> = () => null;
// // $ExpectError
// createRefetchContainer(FunctionComponent, { story: graphql`` }, graphql``);
class ClassComponent extends React.Component<{}> {}
// $ExpectError
createRefetchContainer(ClassComponent, { story: graphql`` }, graphql``);
}
function requiresTheCorrectRelayPropTypeInPropsInterface() {
class ClassComponent1 extends React.Component<{ relay: RelayProp }> {}
// $ExpectError
createRefetchContainer(ClassComponent1, { story: graphql`` }, graphql``);
class ClassComponent2 extends React.Component<{ relay: RelayPaginationProp }> {}
// $ExpectError
createRefetchContainer(ClassComponent2, { story: graphql`` }, graphql``);
class ClassComponent3 extends React.Component<{ relay: undefined }> {}
// $ExpectError
createRefetchContainer(ClassComponent3, { story: graphql`` }, graphql``);
}
function canTakeComponentRef() {
const onLike = (id: string) => console.log(`Liked story #${id}`);
const story: { ' $fragmentRefs': Story_story$ref } = {} as any;
<StoryRefetchContainer story={story} onLike={onLike} componentRef={ref => console.log(ref)} />;
}
return StoryRefetchContainer;
})();
@@ -211,7 +258,7 @@ const Feed = (() => {
ignoreMe?: {};
}
const FeedStoryEdges: React.SFC<{ edges: FeedStory_edges }> = ({ edges }) => (
const FeedStoryEdges: React.FC<{ edges: FeedStory_edges; relay: RelayProp }> = ({ edges }) => (
<div>{edges.map(({ publishedAt }) => publishedAt).join(', ')}</div>
);
@@ -223,7 +270,7 @@ const Feed = (() => {
`,
});
const FeedStories: React.SFC<Props> = ({ feed, onStoryLike, relay }) => {
const FeedStories: React.FC<Props> = ({ feed, onStoryLike, relay }) => {
// TODO: Getting env here for no good reason other than needing to test it works.
// If you have a good relavant example, please update!
relay.environment;
@@ -252,12 +299,51 @@ const Feed = (() => {
`,
});
function doesNotRequireRelayPropToBeProvided() {
function requiresTheRightProps() {
const onStoryLike = (id: string) => console.log(`Liked story #${id}`);
const feed: { ' $fragmentRefs': FeedStories_feed$ref } = {} as any;
<FeedFragmentContainer feed={feed} onStoryLike={onStoryLike} />;
}
function requiresTheCorrectFragmentRef() {
const onStoryLike = (id: string) => console.log(`Liked story #${id}`);
const story: { ' $fragmentRefs': Story_story$ref } = {} as any;
// $ExpectError
<FeedFragmentContainer feed={story} onStoryLike={onStoryLike} />;
}
function doesNotRequireRelayPropToBeProvidedByParent() {
const onStoryLike = (id: string) => console.log(`Liked story #${id}`);
const feed: { ' $fragmentRefs': FeedStories_feed$ref } = {} as any;
const relayProp: RelayProp = {} as any;
// $ExpectError
<FeedFragmentContainer feed={feed} onStoryLike={onStoryLike} relay={relayProp} />;
}
function doesNotRequireTheRelayPropInPropsInterface() {
const FunctionComponent: React.FC<{}> = () => null;
createFragmentContainer(FunctionComponent, {});
class ClassComponent extends React.Component<{}> {}
createFragmentContainer(ClassComponent, {});
}
function requiresTheCorrectRelayPropTypeInPropsInterface() {
class ClassComponent1 extends React.Component<{ relay: RelayRefetchProp }> {}
// $ExpectError
createFragmentContainer(ClassComponent1, {});
class ClassComponent2 extends React.Component<{ relay: RelayPaginationProp }> {}
// $ExpectError
createFragmentContainer(ClassComponent2, {});
}
function canTakeComponentRef() {
const onStoryLike = (id: string) => console.log(`Liked story #${id}`);
const feed: { ' $fragmentRefs': FeedStories_feed$ref } = {} as any;
<FeedFragmentContainer feed={feed} onStoryLike={onStoryLike} componentRef={ref => console.log(ref)} />;
}
return FeedFragmentContainer;
})();
@@ -361,10 +447,53 @@ type UserFeed_user = {
}
);
function doesNotRequireRelayPropToBeProvided() {
function requiresTheRightProps() {
const user: { ' $fragmentRefs': UserFeed_user$ref } = {} as any;
<UserFeedPaginationContainer loadMoreTitle="Load More" user={user} />;
}
function requiresTheCorrectFragmentRef() {
const story: { ' $fragmentRefs': Story_story$ref } = {} as any;
// $ExpectError
<UserFeedPaginationContainer loadMoreTitle="Load More" user={story} />;
}
function doesNotRequireRelayPropToBeProvidedByParent() {
const user: { ' $fragmentRefs': UserFeed_user$ref } = {} as any;
const relayProp: RelayPaginationProp = {} as any;
// $ExpectError
<UserFeedPaginationContainer loadMoreTitle="Load More" user={user} relay={relayProp} />;
}
function requiresTheRelayPropInPropsInterface() {
// FIXME: This should throw a type error, but doesn't
// const FunctionComponent: React.FC<{}> = () => null;
// // $ExpectError
// createPaginationContainer(FunctionComponent, {}, {} as any);
class ClassComponent extends React.Component<{}> {}
// $ExpectError
createPaginationContainer(ClassComponent, {}, {} as any);
}
function requiresTheCorrectRelayPropTypeInPropsInterface() {
class ClassComponent1 extends React.Component<{ relay: RelayProp }> {}
// $ExpectError
createPaginationContainer(ClassComponent1, {}, {} as any);
class ClassComponent2 extends React.Component<{ relay: RelayRefetchProp }> {}
// $ExpectError
createPaginationContainer(ClassComponent2, {}, {} as any);
class ClassComponent3 extends React.Component<{ relay: undefined }> {}
// $ExpectError
createPaginationContainer(ClassComponent3, {}, {} as any);
}
function canTakeComponentRef() {
const user: { ' $fragmentRefs': UserFeed_user$ref } = {} as any;
<UserFeedPaginationContainer loadMoreTitle="Load More" user={user} componentRef={ref => console.log(ref)} />;
}
};
// ~~~~~~~~~~~~~~~~~~~~~
@@ -492,12 +621,17 @@ requestSubscription(
updater: store => {
// Get the notification
const rootField = store.getRootField('markReadNotification');
const notification = !!rootField && rootField.getLinkedRecord('notification');
const notification = !!rootField ? rootField.getLinkedRecord('notification') : null;
// Add it to a connection
const viewer = store.getRoot().getLinkedRecord('viewer');
const notifications = ConnectionHandler.getConnection(viewer, 'notifications');
const edge = ConnectionHandler.createEdge(store, notifications, notification, '<TypeOfNotificationsEdge>');
ConnectionHandler.insertEdgeAfter(notifications, edge);
const notifications = ConnectionHandler.getConnection(viewer!, 'notifications');
const edge = ConnectionHandler.createEdge(
store,
notifications!,
notification!,
'<TypeOfNotificationsEdge>'
);
ConnectionHandler.insertEdgeAfter(notifications!, edge);
},
}
);

View File

@@ -20,10 +20,6 @@
},
"files": [
"index.d.ts",
"classic.d.ts",
"compat.d.ts",
"test/react-relay-tests.tsx",
"test/react-relay-compat-tests.tsx",
"test/react-relay-classic-tests.tsx"
"test/react-relay-tests.tsx"
]
}

View File

@@ -1,7 +1,4 @@
{
"extends": "dtslint/dt.json",
"rules": {
// TODO
"no-unnecessary-class": false
}
"rules": {}
}

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,6 @@ import {
Store,
ConnectionHandler,
ViewerHandler,
RecordSourceInspector,
commitLocalUpdate,
QueryResponseCache,
ROOT_ID,
@@ -65,7 +64,7 @@ function handlerProvider(handle: any) {
// Source
// ~~~~~~~~~~~~~~~~~~~~~
const inspector = new RecordSourceInspector(source);
store.publish(source);
// ~~~~~~~~~~~~~~~~~~~~~
// commitLocalUpdate

View File

@@ -1,7 +1,4 @@
{
"extends": "dtslint/dt.json",
"rules": {
// TODO
"no-unnecessary-class": false
}
"rules": {}
}