Skip to content
This repository has been archived by the owner on Apr 13, 2023. It is now read-only.

Commit

Permalink
Merge pull request #1617 from leoasis/strict_type_check
Browse files Browse the repository at this point in the history
Improve Query and graphql types. Strict type check with Typescript.
  • Loading branch information
leoasis authored Feb 1, 2018
2 parents 4e7a87c + 2cbd088 commit 4b05041
Show file tree
Hide file tree
Showing 53 changed files with 3,268 additions and 2,250 deletions.
2 changes: 2 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

### vNext

* Stricter type checking in the codebase. [#1617](https://github.com/apollographql/react-apollo/pull/1617)
* Improved TS types (even more) in both `Query` component and `graphql` HoC. [#1617](https://github.com/apollographql/react-apollo/pull/1617)
* Fix React Component detection bug in `getDataFromTree` [#1604](https://github.com/apollographql/react-apollo/pull/1604)

### 2.1.0-beta.0
Expand Down
160 changes: 98 additions & 62 deletions examples/typescript/src/schema.json

Large diffs are not rendered by default.

8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"bundlesize": [
{
"path": "./lib/react-apollo.browser.umd.js",
"maxSize": "6 KB"
"maxSize": "6.5 KB"
}
],
"lint-staged": {
Expand Down Expand Up @@ -84,13 +84,17 @@
},
"devDependencies": {
"@types/enzyme": "3.1.8",
"@types/enzyme-adapter-react-16": "^1.0.1",
"@types/graphql": "0.11.7",
"@types/invariant": "2.2.29",
"@types/jest": "22.0.1",
"@types/lodash": "4.14.99",
"@types/object-assign": "4.0.30",
"@types/react": "16.0.34",
"@types/prop-types": "15.5.2",
"@types/react": "16.0.35",
"@types/react-dom": "16.0.3",
"@types/react-test-renderer": "16.0.0",
"@types/recompose": "0.24.4",
"@types/zen-observable": "0.5.3",
"apollo-cache-inmemory": "1.1.5",
"apollo-client": "2.2.1",
Expand Down
4 changes: 2 additions & 2 deletions src/ApolloConsumer.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import * as React from 'react';
import * as PropTypes from 'prop-types';
import ApolloClient from 'apollo-client';
const invariant = require('invariant');
import invariant from 'invariant';

export interface ApolloConsumerProps {
children: (client: ApolloClient<any>) => React.ReactElement<any>;
children: (client: ApolloClient<any>) => React.ReactElement<any> | null;
}

const ApolloConsumer: React.StatelessComponent<ApolloConsumerProps> = (
Expand Down
3 changes: 1 addition & 2 deletions src/ApolloProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import * as PropTypes from 'prop-types';
import { Component } from 'react';
import ApolloClient from 'apollo-client';
import QueryRecyclerProvider from './QueryRecyclerProvider';

const invariant = require('invariant');
import invariant from 'invariant';

export interface ApolloProviderProps<TCache> {
client: ApolloClient<TCache>;
Expand Down
110 changes: 79 additions & 31 deletions src/Query.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,62 @@ import ApolloClient, {
ObservableQuery,
ApolloQueryResult,
ApolloError,
FetchMoreOptions,
UpdateQueryOptions,
FetchMoreQueryOptions,
FetchPolicy,
ApolloCurrentResult,
NetworkStatus,
} from 'apollo-client';
import { DocumentNode } from 'graphql';
import { ZenObservable } from 'zen-observable-ts';
import { OperationVariables } from './types';
import { parser, DocumentType } from './parser';
import pick from 'lodash/pick';
import shallowEqual from 'fbjs/lib/shallowEqual';
import invariant from 'invariant';

const shallowEqual = require('fbjs/lib/shallowEqual');
const invariant = require('invariant');
const pick = require('lodash/pick');
// Improved FetchMoreOptions type, need to port them back to Apollo Client
export interface FetchMoreOptions<TData, TVariables> {
updateQuery: (
previousQueryResult: TData,
options: {
fetchMoreResult?: TData;
variables: TVariables;
},
) => TData;
}

type ObservableQueryFields<TData> = Pick<ObservableQuery<TData>, 'refetch' | 'fetchMore' | 'updateQuery' | 'startPolling' | 'stopPolling'>;
// Improved FetchMoreQueryOptions type, need to port them back to Apollo Client
export interface FetchMoreQueryOptions<TVariables, K extends keyof TVariables> {
variables: Pick<TVariables, K>;
}

// Improved ObservableQuery field types, need to port them back to Apollo Client
export type ObservableQueryFields<TData, TVariables> = Pick<
ObservableQuery<TData>,
'startPolling' | 'stopPolling'
> & {
refetch: (variables?: TVariables) => Promise<ApolloQueryResult<TData>>;
fetchMore: (<K extends keyof TVariables>(
fetchMoreOptions: FetchMoreQueryOptions<TVariables, K> &
FetchMoreOptions<TData, TVariables>,
) => Promise<ApolloQueryResult<TData>>) &
(<TData2, TVariables2, K extends keyof TVariables2>(
fetchMoreOptions: { query: DocumentNode } & FetchMoreQueryOptions<
TVariables2,
K
> &
FetchMoreOptions<TData2, TVariables2>,
) => Promise<ApolloQueryResult<TData2>>);
updateQuery: (
mapFn: (
previousQueryResult: TData,
options: { variables?: TVariables },
) => TData,
) => void;
};

function observableQueryFields<TData>(observable: ObservableQuery<TData>): ObservableQueryFields<TData> {
function observableQueryFields<TData, TVariables>(
observable: ObservableQuery<TData>,
): ObservableQueryFields<TData, TVariables> {
const fields = pick(
observable,
'refetch',
Expand All @@ -32,33 +70,34 @@ function observableQueryFields<TData>(observable: ObservableQuery<TData>): Obser
);

Object.keys(fields).forEach(key => {
if (typeof fields[key] === 'function') {
fields[key] = fields[key].bind(observable);
const k = key as
| 'refetch'
| 'fetchMore'
| 'updateQuery'
| 'startPolling'
| 'stopPolling';
if (typeof fields[k] === 'function') {
fields[k] = fields[k].bind(observable);
}
});

return fields;
// TODO: Need to cast this because we improved the type of `updateQuery` to be parametric
// on variables, while the type in Apollo client just has object.
// Consider removing this when that is properly typed
return fields as ObservableQueryFields<TData, TVariables>;
}

function isDataFilled<TData>(data: {} | TData): data is TData {
return Object.keys(data).length > 0;
}

export interface QueryResult<TData = any, TVariables = OperationVariables> {
export interface QueryResult<TData = any, TVariables = OperationVariables>
extends ObservableQueryFields<TData, TVariables> {
client: ApolloClient<any>;
data?: TData;
error?: ApolloError;
fetchMore: (
fetchMoreOptions: FetchMoreQueryOptions & FetchMoreOptions,
) => Promise<ApolloQueryResult<any>>;
loading: boolean;
networkStatus: number;
refetch: (variables?: TVariables) => Promise<ApolloQueryResult<any>>;
startPolling: (pollInterval: number) => void;
stopPolling: () => void;
updateQuery: (
mapFn: (previousQueryResult: any, options: UpdateQueryOptions) => any,
) => void;
networkStatus: NetworkStatus;
}

export interface QueryProps<TData = any, TVariables = OperationVariables> {
Expand All @@ -75,20 +114,24 @@ export interface QueryState<TData = any> {
result: ApolloCurrentResult<TData>;
}

class Query<TData = any, TVariables = OperationVariables> extends React.Component<
QueryProps<TData, TVariables>,
QueryState<TData>
> {
export interface QueryContext {
client: ApolloClient<Object>;
}

class Query<
TData = any,
TVariables = OperationVariables
> extends React.Component<QueryProps<TData, TVariables>, QueryState<TData>> {
static contextTypes = {
client: PropTypes.object.isRequired,
};
context: QueryContext;

state: QueryState<TData>;
private client: ApolloClient<any>;
private client: ApolloClient<Object>;
private queryObservable: ObservableQuery<TData>;
private querySubscription: ZenObservable.Subscription;

constructor(props: QueryProps<TData, TVariables>, context: any) {
constructor(props: QueryProps<TData, TVariables>, context: QueryContext) {
super(props, context);

invariant(
Expand Down Expand Up @@ -130,7 +173,10 @@ class Query<TData = any, TVariables = OperationVariables> extends React.Componen
this.startQuerySubscription();
}

componentWillReceiveProps(nextProps, nextContext) {
componentWillReceiveProps(
nextProps: QueryProps<TData, TVariables>,
nextContext: QueryContext,
) {
if (
shallowEqual(this.props, nextProps) &&
this.client === nextContext.client
Expand All @@ -157,7 +203,9 @@ class Query<TData = any, TVariables = OperationVariables> extends React.Componen
return children(queryResult);
}

private initializeQueryObservable = props => {
private initializeQueryObservable = (
props: QueryProps<TData, TVariables>,
) => {
const {
variables,
pollInterval,
Expand Down
35 changes: 24 additions & 11 deletions src/Subscriptions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,16 @@ const invariant = require('invariant');
export interface SubscriptionResult<TData = any> {
loading: boolean;
data?: TData;
error: ApolloError;
error?: ApolloError;
}

export interface SubscriptionProps {
export interface SubscriptionProps<
TData = any,
TVariables = OperationVariables
> {
query: DocumentNode;
variables?: OperationVariables;
children: (result: any) => React.ReactNode;
variables?: TVariables;
children: (result: SubscriptionResult<TData>) => React.ReactNode;
}

export interface SubscriptionState<TData = any> {
Expand All @@ -28,8 +31,12 @@ export interface SubscriptionState<TData = any> {
error?: ApolloError;
}

class Subscription<TData = any> extends React.Component<
SubscriptionProps,
export interface SubscriptionContext {
client: ApolloClient<Object>;
}

class Subscription<TData = any, TVariables = any> extends React.Component<
SubscriptionProps<TData, TVariables>,
SubscriptionState<TData>
> {
static contextTypes = {
Expand All @@ -40,7 +47,10 @@ class Subscription<TData = any> extends React.Component<
private queryObservable: Observable<any>;
private querySubscription: ZenObservable.Subscription;

constructor(props: SubscriptionProps, context: any) {
constructor(
props: SubscriptionProps<TData, TVariables>,
context: SubscriptionContext,
) {
super(props, context);

invariant(
Expand All @@ -56,7 +66,10 @@ class Subscription<TData = any> extends React.Component<
this.startSubscription();
}

componentWillReceiveProps(nextProps, nextContext) {
componentWillReceiveProps(
nextProps: SubscriptionProps<TData, TVariables>,
nextContext: SubscriptionContext,
) {
if (
shallowEqual(this.props, nextProps) &&
this.client === nextContext.client
Expand Down Expand Up @@ -88,7 +101,7 @@ class Subscription<TData = any> extends React.Component<
return this.props.children(result);
}

private initialize = props => {
private initialize = (props: SubscriptionProps<TData, TVariables>) => {
this.queryObservable = this.client.subscribe({
query: props.query,
variables: props.variables,
Expand All @@ -110,15 +123,15 @@ class Subscription<TData = any> extends React.Component<
};
};

private updateCurrentData = result => {
private updateCurrentData = (result: SubscriptionResult<TData>) => {
this.setState({
data: result.data,
loading: false,
error: undefined,
});
};

private updateError = error => {
private updateError = (error: any) => {
this.setState({
error,
loading: false,
Expand Down
Loading

0 comments on commit 4b05041

Please sign in to comment.