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

<Subscription /> <Query /> & <Mutation /> consume the apollo client the same way. #1956

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <br/>
[@hwillson](https://github.com/hwillson) in [#2404](https://github.com/apollographql/react-apollo/pull/2404)
- Make sure `<Subscription />`, `<Query />` & `<Mutation />` all support
using an Apollo Client instance configured in the `context` or via
props. <br/>
[@quentin-](https://github.com/quentin-) in [#1956](https://github.com/apollographql/react-apollo/pull/1956)

## 2.1.11 (August 9, 2018)

Expand Down
30 changes: 9 additions & 21 deletions src/Mutation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<TData = Record<string, any>> {
data?: TData;
Expand All @@ -17,7 +18,7 @@ export interface MutationResult<TData = Record<string, any>> {
client: ApolloClient<Object>;
}
export interface MutationContext {
client: ApolloClient<Object>;
client?: ApolloClient<Object>;
operations: Map<string, { query: DocumentNode; variables: any }>;
}

Expand Down Expand Up @@ -54,6 +55,7 @@ export declare type MutationFn<TData = any, TVariables = OperationVariables> = (
) => Promise<void | FetchResult<TData>>;

export interface MutationProps<TData = any, TVariables = OperationVariables> {
client?: ApolloClient<Object>;
mutation: DocumentNode;
ignoreResults?: boolean;
optimisticResponse?: Object;
Expand All @@ -67,7 +69,6 @@ export interface MutationProps<TData = any, TVariables = OperationVariables> {
) => React.ReactNode;
onCompleted?: (data: TData) => void;
onError?: (error: ApolloError) => void;
client?: ApolloClient<Object>;
context?: Record<string, any>;
}

Expand Down Expand Up @@ -116,17 +117,8 @@ class Mutation<TData = any, TVariables = OperationVariables> extends React.Compo

constructor(props: MutationProps<TData, TVariables>, 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 <ApolloProvider>, or pass an ApolloClient ' +
'instance in via props.',
);

this.client = getClient(props, context);
this.verifyDocumentIsMutation(props.mutation);

this.mostRecentMutationId = 0;
this.state = initialState;
}
Expand All @@ -143,20 +135,17 @@ class Mutation<TData = any, TVariables = OperationVariables> extends React.Compo
nextProps: MutationProps<TData, TVariables>,
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;
}

if (this.props.mutation !== nextProps.mutation) {
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);
}
}
Expand All @@ -178,7 +167,6 @@ class Mutation<TData = any, TVariables = OperationVariables> extends React.Compo

private runMutation = (options: MutationOptions<TVariables> = {}) => {
this.onMutationStart();

const mutationId = this.generateNewMutationId();

return this.mutate(options)
Expand Down
30 changes: 11 additions & 19 deletions src/Query.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -97,19 +98,20 @@ export interface QueryProps<TData = any, TVariables = OperationVariables> {
}

export interface QueryContext {
client: ApolloClient<Object>;
client?: ApolloClient<Object>;
operations?: Map<string, { query: DocumentNode; variables: any }>;
}

export default class Query<TData = any, TVariables = OperationVariables> extends React.Component<
QueryProps<TData, TVariables>
> {
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,
Expand Down Expand Up @@ -143,11 +145,7 @@ export default class Query<TData = any, TVariables = OperationVariables> extends
constructor(props: QueryProps<TData, TVariables>, 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 <ApolloProvider>`,
);
this.client = getClient(props, context);
this.initializeQueryObservable(props);
}

Expand Down Expand Up @@ -192,26 +190,20 @@ export default class Query<TData = any, TVariables = OperationVariables> 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();
}
Expand Down
18 changes: 9 additions & 9 deletions src/Subscriptions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -25,6 +26,7 @@ export interface SubscriptionProps<TData = any, TVariables = OperationVariables>
subscription: DocumentNode;
variables?: TVariables;
shouldResubscribe?: any;
client?: ApolloClient<Object>;
onSubscriptionData?: (options: OnSubscriptionDataOptions<TData>) => any;
children?: (result: SubscriptionResult<TData>) => React.ReactNode;
}
Expand All @@ -36,7 +38,7 @@ export interface SubscriptionState<TData = any> {
}

export interface SubscriptionContext {
client: ApolloClient<Object>;
client?: ApolloClient<Object>;
}

class Subscription<TData = any, TVariables = any> extends React.Component<
Expand All @@ -62,11 +64,7 @@ class Subscription<TData = any, TVariables = any> extends React.Component<
constructor(props: SubscriptionProps<TData, TVariables>, context: SubscriptionContext) {
super(props, context);

invariant(
!!context.client,
`Could not find "client" in the context of Subscription. Wrap the root component in an <ApolloProvider>`,
);
this.client = context.client;
this.client = getClient(props, context);
this.initialize(props);
this.state = this.getInitialState();
}
Expand All @@ -79,9 +77,11 @@ class Subscription<TData = any, TVariables = any> extends React.Component<
nextProps: SubscriptionProps<TData, TVariables>,
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;
Expand All @@ -92,8 +92,8 @@ class Subscription<TData = any, TVariables = any> 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) {
Expand Down
26 changes: 26 additions & 0 deletions src/component-utils.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import ApolloClient from 'apollo-client';
import invariant from 'invariant';

export interface CommonComponentProps {
client?: ApolloClient<Object>;
}

export interface CommonComponentContext {
client?: ApolloClient<Object>;
}

export function getClient(
props: CommonComponentProps,
context: CommonComponentContext,
): ApolloClient<Object> {
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 <ApolloProvider>, or pass an ' +
'ApolloClient instance in via props.',
);

return client as ApolloClient<Object>;
}
87 changes: 86 additions & 1 deletion test/client/Mutation.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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 (
<ApolloProvider client={contextClient}>
<Mutation client={props.propsClient} mutation={mutation}>
{createTodo => <button onClick={() => createTodo().then(spy)} />}
</Mutation>
</ApolloProvider>
);
};

const wrapper = mount(<Component />);
const button = wrapper.find('button').first();

// context client mutation
button.simulate('click');

// props client mutation
wrapper.setProps({ propsClient });
button.simulate('click');

// context client mutation
wrapper.setProps({ propsClient: undefined });
button.simulate('click');

// props client mutation
wrapper.setProps({ propsClient });
button.simulate('click');

setTimeout(() => {
expect(spy).toHaveBeenCalledTimes(4);
expect(spy).toHaveBeenCalledWith(mocksContext[0].result);
expect(spy).toHaveBeenCalledWith(mocksProps[0].result);
expect(spy).toHaveBeenCalledWith(mocksContext[1].result);
expect(spy).toHaveBeenCalledWith(mocksProps[1].result);

done();
}, 10);
});

it('performs a mutation', done => {
let count = 0;
const Component = () => (
Expand Down
Loading