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

Commit

Permalink
Implemented the ApolloConsumer component. (#1399)
Browse files Browse the repository at this point in the history
* Implemented the ApolloConsumer component.

* Wrapped the withApollo component in the ApolloConsumer component.

* Updated the changelog

* Removed render prop in favour of children render prop
  • Loading branch information
excitement-engineer authored and rosskevin committed Dec 26, 2017
1 parent 08b8077 commit 1ae46f1
Show file tree
Hide file tree
Showing 13 changed files with 196 additions and 117 deletions.
3 changes: 2 additions & 1 deletion Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ a) full typing; and b) ease of use; and c) consistency. New parameterized is:
first three params (`TChildProps` can be derived). [#1402](https://github.com/apollographql/react-apollo/pull/1402)
- Typescript - fix `graphql` HOC inference [#1402](https://github.com/apollographql/react-apollo/pull/1402)
- **Remove deprecated** `operationOptions.options.skip`, use `operationOptions.skip` instead
- Added <Query /> component [#1399](https://github.com/apollographql/react-apollo/pull/1398)
- Added <Query /> component [#1398](https://github.com/apollographql/react-apollo/pull/1398)
- Made prettier solely responsible for formatting, removed all formatting linting rules from tslint [#1452](https://github.com/apollographql/react-apollo/pull/1452)
- add <ApolloConsumer /> component [#1399](https://github.com/apollographql/react-apollo/pull/1399)

### 2.0.4
- rolled back on the lodash-es changes from
Expand Down
6 changes: 4 additions & 2 deletions examples/typescript/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
"build": "react-scripts-ts build",
"test": "react-scripts-ts test --env=jsdom",
"eject": "react-scripts-ts eject",
"schema": "apollo-codegen introspect-schema https://mpjk0plp9.lp.gql.zone/graphql --output ./src/schema.json",
"types": "apollo-codegen generate ./src/**/*.tsx --schema ./src/schema.json --target typescript --output ./src/schema.ts --add-typename"
"schema":
"apollo-codegen introspect-schema https://mpjk0plp9.lp.gql.zone/graphql --output ./src/schema.json",
"types":
"apollo-codegen generate ./src/**/*.tsx --schema ./src/schema.json --target typescript --output ./src/schema.ts --add-typename"
}
}
26 changes: 26 additions & 0 deletions src/ApolloConsumer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import * as React from 'react';
import * as PropTypes from 'prop-types';
import ApolloClient from 'apollo-client';
const invariant = require('invariant');

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

const ApolloConsumer: React.StatelessComponent<ApolloConsumerProps> = (
props,
context,
) => {
invariant(
!!context.client,
`Could not find "client" in the context of ApolloConsumer. Wrap the root component in an <ApolloProvider>`,
);

return props.children(context.client);
};

ApolloConsumer.contextTypes = {
client: PropTypes.object.isRequired,
};

export default ApolloConsumer;
7 changes: 4 additions & 3 deletions src/Query.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,9 +136,10 @@ class Query extends React.Component<QueryProps, QueryState> {

invariant(
operation.type === DocumentType.Query,
`The <Query /> component requires a graphql query, but got a ${
operation.type === DocumentType.Mutation ? 'mutation' : 'subscription'
}.`,
`The <Query /> component requires a graphql query, but got a ${operation.type ===
DocumentType.Mutation
? 'mutation'
: 'subscription'}.`,
);

const clientOptions = {
Expand Down
4 changes: 1 addition & 3 deletions src/getDataFromTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,9 +170,7 @@ export function getDataFromTree(
errors.length === 1
? errors[0]
: new Error(
`${
errors.length
} errors were thrown when executing your GraphQL queries.`,
`${errors.length} errors were thrown when executing your GraphQL queries.`,
);
error.queryErrors = errors;
throw error;
Expand Down
5 changes: 2 additions & 3 deletions src/graphql.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -303,9 +303,8 @@ export default function graphql<
`The operation '${operation.name}' wrapping '${getDisplayName(
WrappedComponent,
)}' ` +
`is expecting a variable: '${
variable.name.value
}' but it was not found in the props ` +
`is expecting a variable: '${variable.name
.value}' but it was not found in the props ` +
`passed to '${graphQLDisplayName}'`,
);
}
Expand Down
42 changes: 18 additions & 24 deletions src/withApollo.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import { Component, createElement } from 'react';
import * as PropTypes from 'prop-types';
import * as React from 'react';

const invariant = require('invariant');
const assign = require('object-assign');

const hoistNonReactStatics = require('hoist-non-react-statics');

import ApolloClient from 'apollo-client';

import { OperationOption } from './types';
import ApolloConsumer from './ApolloConsumer';

function getDisplayName(WrappedComponent) {
return WrappedComponent.displayName || WrappedComponent.name || 'Component';
Expand All @@ -20,28 +17,16 @@ export function withApollo<TProps, TResult>(
) {
const withDisplayName = `withApollo(${getDisplayName(WrappedComponent)})`;

class WithApollo extends Component<any, any> {
class WithApollo extends React.Component<any, any> {
static displayName = withDisplayName;
static WrappedComponent = WrappedComponent;
static contextTypes = { client: PropTypes.object.isRequired };

// data storage
private client: ApolloClient<any>; // apollo client

// wrapped instance
private wrappedInstance: any;

constructor(props, context) {
super(props, context);
this.client = context.client;
constructor(props) {
super(props);
this.setWrappedInstance = this.setWrappedInstance.bind(this);

invariant(
!!this.client,
`Could not find "client" in the context of ` +
`"${withDisplayName}". ` +
`Wrap the root component in an <ApolloProvider>`,
);
}

getWrappedInstance() {
Expand All @@ -59,10 +44,19 @@ export function withApollo<TProps, TResult>(
}

render() {
const props = assign({}, this.props);
props.client = this.client;
if (operationOptions.withRef) props.ref = this.setWrappedInstance;
return createElement(WrappedComponent, props);
return (
<ApolloConsumer>
{client => (
<WrappedComponent
{...this.props}
client={client}
ref={
operationOptions.withRef ? this.setWrappedInstance : undefined
}
/>
)}
</ApolloConsumer>
);
}
}

Expand Down
33 changes: 17 additions & 16 deletions test/flow-usage.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,22 +143,23 @@ export type InputProps = {
episode: string,
};

const withCharacter: OperationComponent<Response, InputProps, Props> = graphql(
HERO_QUERY,
{
options: ({ episode }) => ({
// $ExpectError [string] This type cannot be compared to number
variables: { episode: episode > 1 },
}),
props: ({ data, ownProps }) => ({
...data,
// $ExpectError [string] This type cannot be compared to number
episode: ownProps.episode > 1,
// $ExpectError property `isHero`. Property not found on object type
isHero: data && data.hero && data.hero.isHero,
}),
},
);
const withCharacter: OperationComponent<
Response,
InputProps,
Props,
> = graphql(HERO_QUERY, {
options: ({ episode }) => ({
// $ExpectError [string] This type cannot be compared to number
variables: { episode: episode > 1 },
}),
props: ({ data, ownProps }) => ({
...data,
// $ExpectError [string] This type cannot be compared to number
episode: ownProps.episode > 1,
// $ExpectError property `isHero`. Property not found on object type
isHero: data && data.hero && data.hero.isHero,
}),
});

export default withCharacter(({ loading, hero, error }) => {
if (loading) return <div>Loading</div>;
Expand Down
12 changes: 6 additions & 6 deletions test/react-native/component.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,12 @@ describe('App', () => {
cache: new Cache({ addTypename: false }),
});

const ContainerWithData = graphql(query)(
({ data }: ChildProps<{}, Data>) => {
if (data.loading) return <Text>Loading...</Text>;
return <Text>{data.allPeople.people.name}</Text>;
},
);
const ContainerWithData = graphql(
query,
)(({ data }: ChildProps<{}, Data>) => {
if (data.loading) return <Text>Loading...</Text>;
return <Text>{data.allPeople.people.name}</Text>;
});
const output = renderer.create(
<ApolloProvider client={client}>
<ContainerWithData />
Expand Down
57 changes: 57 additions & 0 deletions test/react-web/client/ApolloConsumer.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React from 'react';
import ApolloClient from 'apollo-client';
import { InMemoryCache as Cache } from 'apollo-cache-inmemory';
import { ApolloLink } from 'apollo-link';

import ApolloProvider from '../../../src/ApolloProvider';
import ApolloConsumer from '../../../src/ApolloConsumer';
import { mount } from 'enzyme';
import invariant from 'invariant';

const client = new ApolloClient({
cache: new Cache(),
link: new ApolloLink((o, f) => f(o)),
});

describe('<ApolloConsumer /> component', () => {
it('has a render prop', done => {
mount(
<ApolloProvider client={client}>
<ApolloConsumer>
{clientRender => {
try {
expect(clientRender).toBe(client);
done();
} catch (e) {
done.fail(e);
}
return null;
}}
</ApolloConsumer>
</ApolloProvider>,
);
});

it('renders the content in the render prop', () => {
const wrapper = mount(
<ApolloProvider client={client}>
<ApolloConsumer>{clientRender => <div />}</ApolloConsumer>
</ApolloProvider>,
);

expect(wrapper.find('div').exists()).toBeTruthy();
});

it('errors if there is no client in the context', () => {
// Prevent Error about missing context type from appearing in the console.
const errorLogger = console.error;
console.error = () => {};
expect(() => {
mount(<ApolloConsumer render={client => null} />);
}).toThrowError(
'Could not find "client" in the context of ApolloConsumer. Wrap the root component in an <ApolloProvider>',
);

console.error = errorLogger;
});
});
28 changes: 14 additions & 14 deletions test/react-web/client/graphql/queries/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,13 @@ describe('queries', () => {
};
}

const ContainerWithData = graphql<any, Data>(query)(
({ data }: DataProps<Data>) => {
expect(data).toBeTruthy();
expect(data.loading).toBeTruthy();
return null;
},
);
const ContainerWithData = graphql<any, Data>(
query,
)(({ data }: DataProps<Data>) => {
expect(data).toBeTruthy();
expect(data.loading).toBeTruthy();
return null;
});

const output = renderer.create(
<ApolloProvider client={client}>
Expand Down Expand Up @@ -95,13 +95,13 @@ describe('queries', () => {
first: number;
}

const ContainerWithData = graphql<any, Data, Variables>(query)(
({ data }: DataProps<Data, Variables>) => {
expect(data).toBeTruthy();
expect(data.variables).toEqual(variables);
return null;
},
);
const ContainerWithData = graphql<any, Data, Variables>(
query,
)(({ data }: DataProps<Data, Variables>) => {
expect(data).toBeTruthy();
expect(data.variables).toEqual(variables);
return null;
});

renderer.create(
<ApolloProvider client={client}>
Expand Down
30 changes: 15 additions & 15 deletions test/react-web/client/graphql/subscriptions.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,14 @@ describe('subscriptions', () => {
user: { name: string };
}

const ContainerWithData = graphql<Props, Data>(query)(
({ data }: ChildProps<Props, Data>) => {
expect(data).toBeTruthy();
expect(data.user).toBeFalsy();
expect(data.loading).toBeTruthy();
return null;
},
);
const ContainerWithData = graphql<Props, Data>(
query,
)(({ data }: ChildProps<Props, Data>) => {
expect(data).toBeTruthy();
expect(data.user).toBeFalsy();
expect(data.loading).toBeTruthy();
return null;
});

const output = renderer.create(
<ApolloProvider client={client}>
Expand Down Expand Up @@ -84,13 +84,13 @@ describe('subscriptions', () => {
user: { name: string };
}

const ContainerWithData = graphql<Props, Data>(query)(
({ data }: ChildProps<Props, Data>) => {
expect(data).toBeTruthy();
expect(data.variables).toEqual(variables);
return null;
},
);
const ContainerWithData = graphql<Props, Data>(
query,
)(({ data }: ChildProps<Props, Data>) => {
expect(data).toBeTruthy();
expect(data.variables).toEqual(variables);
return null;
});

const output = renderer.create(
<ApolloProvider client={client}>
Expand Down
Loading

0 comments on commit 1ae46f1

Please sign in to comment.