diff --git a/Changelog.md b/Changelog.md index cd4cf3a0c5..13dd939577 100644 --- a/Changelog.md +++ b/Changelog.md @@ -42,6 +42,10 @@ [@edorivai](https://github.com/edorivai) in [#1916](https://github.com/apollographql/react-apollo/pull/1916) - No longer building against Node 9
[@hwillson](https://github.com/hwillson) in [#2404](https://github.com/apollographql/react-apollo/pull/2404) +- Make sure ``, `` & `` all support + using an Apollo Client instance configured in the `context` or via + props.
+ [@quentin-](https://github.com/quentin-) in [#1956](https://github.com/apollographql/react-apollo/pull/1956) ## 2.1.11 (August 9, 2018) diff --git a/src/Mutation.tsx b/src/Mutation.tsx index 60cca1b902..6a226280ac 100644 --- a/src/Mutation.tsx +++ b/src/Mutation.tsx @@ -2,12 +2,13 @@ import React from 'react'; import PropTypes from 'prop-types'; import ApolloClient, { PureQueryOptions, ApolloError } from 'apollo-client'; import { DataProxy } from 'apollo-cache'; -const invariant = require('invariant'); +import invariant from 'invariant'; import { DocumentNode, GraphQLError } from 'graphql'; const shallowEqual = require('fbjs/lib/shallowEqual'); import { OperationVariables, RefetchQueriesProviderFn } from './types'; import { parser, DocumentType } from './parser'; +import { getClient } from './component-utils'; export interface MutationResult> { data?: TData; @@ -17,7 +18,7 @@ export interface MutationResult> { client: ApolloClient; } export interface MutationContext { - client: ApolloClient; + client?: ApolloClient; operations: Map; } @@ -54,6 +55,7 @@ export declare type MutationFn = ( ) => Promise>; export interface MutationProps { + client?: ApolloClient; mutation: DocumentNode; ignoreResults?: boolean; optimisticResponse?: Object; @@ -67,7 +69,6 @@ export interface MutationProps { ) => React.ReactNode; onCompleted?: (data: TData) => void; onError?: (error: ApolloError) => void; - client?: ApolloClient; context?: Record; } @@ -116,17 +117,8 @@ class Mutation extends React.Compo constructor(props: MutationProps, context: any) { super(props, context); - - this.client = props.client || context.client; - invariant( - !!this.client, - 'Could not find "client" in the context or props of Mutation. Wrap ' + - 'the root component in an , or pass an ApolloClient ' + - 'instance in via props.', - ); - + this.client = getClient(props, context); this.verifyDocumentIsMutation(props.mutation); - this.mostRecentMutationId = 0; this.state = initialState; } @@ -143,11 +135,8 @@ class Mutation extends React.Compo nextProps: MutationProps, nextContext: MutationContext, ) { - const { client } = nextProps; - if ( - shallowEqual(this.props, nextProps) && - (this.client === client || this.client === nextContext.client) - ) { + const nextClient = getClient(nextProps, nextContext); + if (shallowEqual(this.props, nextProps) && this.client === nextClient) { return; } @@ -155,8 +144,8 @@ class Mutation extends React.Compo this.verifyDocumentIsMutation(nextProps.mutation); } - if (this.client !== client && this.client !== nextContext.client) { - this.client = client || nextContext.client; + if (this.client !== nextClient) { + this.client = nextClient; this.setState(initialState); } } @@ -178,7 +167,6 @@ class Mutation extends React.Compo private runMutation = (options: MutationOptions = {}) => { this.onMutationStart(); - const mutationId = this.generateNewMutationId(); return this.mutate(options) diff --git a/src/Query.tsx b/src/Query.tsx index f89c8aa179..bc97a62991 100644 --- a/src/Query.tsx +++ b/src/Query.tsx @@ -14,6 +14,7 @@ import { DocumentNode } from 'graphql'; import { ZenObservable } from 'zen-observable-ts'; import { OperationVariables, GraphqlQueryControls } from './types'; import { parser, DocumentType, IDocumentDefinition } from './parser'; +import { getClient } from './component-utils'; const shallowEqual = require('fbjs/lib/shallowEqual'); const invariant = require('invariant'); @@ -97,7 +98,7 @@ export interface QueryProps { } export interface QueryContext { - client: ApolloClient; + client?: ApolloClient; operations?: Map; } @@ -105,11 +106,12 @@ export default class Query extends QueryProps > { static contextTypes = { - client: PropTypes.object.isRequired, + client: PropTypes.object, operations: PropTypes.object, }; static propTypes = { + client: PropTypes.object, children: PropTypes.func.isRequired, fetchPolicy: PropTypes.string, notifyOnNetworkStatusChange: PropTypes.bool, @@ -143,11 +145,7 @@ export default class Query extends constructor(props: QueryProps, context: QueryContext) { super(props, context); - this.client = props.client || context.client; - invariant( - !!this.client, - `Could not find "client" in the context of Query or as passed props. Wrap the root component in an `, - ); + this.client = getClient(props, context); this.initializeQueryObservable(props); } @@ -192,26 +190,20 @@ export default class Query extends return; } - const { client } = nextProps; - if ( - shallowEqual(this.props, nextProps) && - (this.client === client || this.client === nextContext.client) - ) { + const nextClient = getClient(nextProps, nextContext); + + if (shallowEqual(this.props, nextProps) && this.client === nextClient) { return; } - if (this.client !== client && this.client !== nextContext.client) { - if (client) { - this.client = client; - } else { - this.client = nextContext.client; - } + if (this.client !== nextClient) { + this.client = nextClient; this.removeQuerySubscription(); this.queryObservable = null; this.previousData = {}; - this.updateQuery(nextProps); } + if (this.props.query !== nextProps.query) { this.removeQuerySubscription(); } diff --git a/src/Subscriptions.tsx b/src/Subscriptions.tsx index a6dc4b5958..d6f61c2fc4 100644 --- a/src/Subscriptions.tsx +++ b/src/Subscriptions.tsx @@ -6,6 +6,7 @@ import { Observable } from 'apollo-link'; import { DocumentNode } from 'graphql'; import { ZenObservable } from 'zen-observable-ts'; import { OperationVariables } from './types'; +import { getClient } from './component-utils'; const shallowEqual = require('fbjs/lib/shallowEqual'); const invariant = require('invariant'); @@ -25,6 +26,7 @@ export interface SubscriptionProps subscription: DocumentNode; variables?: TVariables; shouldResubscribe?: any; + client?: ApolloClient; onSubscriptionData?: (options: OnSubscriptionDataOptions) => any; children?: (result: SubscriptionResult) => React.ReactNode; } @@ -36,7 +38,7 @@ export interface SubscriptionState { } export interface SubscriptionContext { - client: ApolloClient; + client?: ApolloClient; } class Subscription extends React.Component< @@ -62,11 +64,7 @@ class Subscription extends React.Component< constructor(props: SubscriptionProps, context: SubscriptionContext) { super(props, context); - invariant( - !!context.client, - `Could not find "client" in the context of Subscription. Wrap the root component in an `, - ); - this.client = context.client; + this.client = getClient(props, context); this.initialize(props); this.state = this.getInitialState(); } @@ -79,9 +77,11 @@ class Subscription extends React.Component< nextProps: SubscriptionProps, nextContext: SubscriptionContext, ) { + const nextClient = getClient(nextProps, nextContext); + if ( shallowEqual(this.props.variables, nextProps.variables) && - this.client === nextContext.client && + this.client === nextClient && this.props.subscription === nextProps.subscription ) { return; @@ -92,8 +92,8 @@ class Subscription extends React.Component< shouldResubscribe = !!shouldResubscribe(this.props, nextProps); } const shouldNotResubscribe = shouldResubscribe === false; - if (this.client !== nextContext.client) { - this.client = nextContext.client; + if (this.client !== nextClient) { + this.client = nextClient; } if (!shouldNotResubscribe) { diff --git a/src/component-utils.tsx b/src/component-utils.tsx new file mode 100644 index 0000000000..149abfa8ad --- /dev/null +++ b/src/component-utils.tsx @@ -0,0 +1,26 @@ +import ApolloClient from 'apollo-client'; +import invariant from 'invariant'; + +export interface CommonComponentProps { + client?: ApolloClient; +} + +export interface CommonComponentContext { + client?: ApolloClient; +} + +export function getClient( + props: CommonComponentProps, + context: CommonComponentContext, +): ApolloClient { + const client = props.client || context.client; + + invariant( + !!client, + 'Could not find "client" in the context or passed in as a prop. ' + + 'Wrap the root component in an , or pass an ' + + 'ApolloClient instance in via props.', + ); + + return client as ApolloClient; +} diff --git a/test/client/Mutation.test.tsx b/test/client/Mutation.test.tsx index 9d32adee9f..8c1e456098 100644 --- a/test/client/Mutation.test.tsx +++ b/test/client/Mutation.test.tsx @@ -7,7 +7,7 @@ import { DataProxy } from 'apollo-cache'; import { ExecutionResult } from 'graphql'; import { ApolloProvider, Mutation, Query } from '../../src'; -import { MockedProvider, mockSingleLink } from '../../src/test-utils'; +import { MockedProvider, MockLink, mockSingleLink } from '../../src/test-utils'; import stripSymbols from '../test-utils/stripSymbols'; @@ -66,6 +66,91 @@ const mocks = [ const cache = new Cache({ addTypename: false }); +it('pick prop client over context client', async done => { + const mock = (text: string) => [ + { + request: { query: mutation }, + result: { + data: { + createTodo: { + __typename: 'Todo', + id: '99', + text, + completed: true, + }, + __typename: 'Mutation', + }, + }, + }, + { + request: { query: mutation }, + result: { + data: { + createTodo: { + __typename: 'Todo', + id: '100', + text, + completed: true, + }, + __typename: 'Mutation', + }, + }, + }, + ]; + + const mocksProps = mock('This is the result of the prop client mutation.'); + const mocksContext = mock('This is the result of the context client mutation.'); + + function mockClient(m: any) { + return new ApolloClient({ + link: new MockLink(m, false), + cache: new Cache({ addTypename: false }), + }); + } + + const contextClient = mockClient(mocksContext); + const propsClient = mockClient(mocksProps); + const spy = jest.fn(); + + const Component = (props: any) => { + return ( + + + {createTodo =>