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

v2.1 Implemented the Query Component #1398

Merged
merged 16 commits into from
Dec 22, 2017
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
1 change: 1 addition & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ 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)

### 2.0.4
- rolled back on the lodash-es changes from
Expand Down
21 changes: 21 additions & 0 deletions examples/components/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# See https://help.github.com/ignore-files/ for more about ignoring files.

# dependencies
/node_modules

# testing
/coverage

# production
/build

# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local

npm-debug.log*
yarn-debug.log*
yarn-error.log*
20 changes: 20 additions & 0 deletions examples/components/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "components",
"version": "0.1.0",
"private": true,
"dependencies": {
"apollo-client-preset": "^1.0.5",
"graphql": "^0.12.0",
"graphql-tag": "^2.6.0",
"react": "^16.2.0",
"react-apollo": "file:../..",
"react-dom": "^16.2.0",
"react-scripts": "1.0.17"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
}
}
Binary file added examples/components/public/favicon.ico
Binary file not shown.
40 changes: 40 additions & 0 deletions examples/components/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<!--
manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
15 changes: 15 additions & 0 deletions examples/components/public/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": "./index.html",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
61 changes: 61 additions & 0 deletions examples/components/src/App.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React from 'react';
import { Query } from 'react-apollo';
import gql from 'graphql-tag';

export const HERO_QUERY = gql`
query GetCharacter($episode: Episode!) {
hero(episode: $episode) {
name
id
friends {
name
id
appearsIn
}
}
}
`;

const Character = ({ episode }) => (
<Query
query={HERO_QUERY}
variables={{ episode }}
render={result => {
if (result.loading) {
return <div>Loading</div>;
}
if (result.error) {
return <h1>ERROR</h1>;
}
const { hero } = result.data;
return (
<div>
{hero && (
<div>
<h3>{hero.name}</h3>

{hero.friends &&
hero.friends.map(
friend =>
friend && (
<h6 key={friend.id}>
{friend.name}:{' '}
{friend.appearsIn
.map(x => x && x.toLowerCase())
.join(', ')}
</h6>
),
)}
</div>
)}
</div>
);
}}
/>
);

export const App = () => (
<div>
<Character episode="EMPIRE" />
</div>
);
21 changes: 21 additions & 0 deletions examples/components/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react';
import { render } from 'react-dom';
import { ApolloClient } from 'apollo-client';
import { HttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { ApolloProvider } from 'react-apollo';

import { App } from './App';

const client = new ApolloClient({
link: new HttpLink({ uri: 'https://mpjk0plp9.lp.gql.zone/graphql' }),
cache: new InMemoryCache(),
});

const WrappedApp = (
<ApolloProvider client={client}>
<App />
</ApolloProvider>
);

render(WrappedApp, document.getElementById('root'));
190 changes: 190 additions & 0 deletions src/Query.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
import * as React from 'react';
import * as PropTypes from 'prop-types';
import ApolloClient, {
ObservableQuery,
ApolloQueryResult,
ApolloError,
FetchMoreOptions,
UpdateQueryOptions,
FetchMoreQueryOptions,
FetchPolicy,
} from 'apollo-client';
import { DocumentNode } from 'graphql';
import { ZenObservable } from 'zen-observable-ts';

import shallowEqual from './shallowEqual';
const invariant = require('invariant');
const pick = require('lodash.pick');

import { OperationVariables } from './types';
import { parser, DocumentType } from './parser';

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

export interface QueryProps {
query: DocumentNode;
variables?: OperationVariables;
fetchPolicy?: FetchPolicy;
pollInterval?: number;
notifyOnNetworkStatusChange?: boolean;
children: (result: QueryResult) => React.ReactNode;
}

export interface QueryState {
result: any;
}

function observableQueryFields(observable) {
const fields = pick(
observable,
'variables',
'refetch',
'fetchMore',
'updateQuery',
'startPolling',
'stopPolling',
);

Object.keys(fields).forEach(key => {
if (typeof fields[key] === 'function') {
fields[key] = fields[key].bind(observable);
}
});

return fields;
}

class Query extends React.Component<QueryProps, QueryState> {
static contextTypes = {
client: PropTypes.object.isRequired,
};

private client: ApolloClient<any>;
private queryObservable: ObservableQuery<any>;
private querySubscription: ZenObservable.Subscription;

constructor(props, context) {
super(props, context);

invariant(
!!context.client,
`Could not find "client" in the context of Query. Wrap the root component in an <ApolloProvider>`,
);
this.client = context.client;

this.initializeQueryObservable(props);
this.state = {
result: this.queryObservable.currentResult(),
};
}

componentDidMount() {
this.startQuerySubscription();
}

componentWillReceiveProps(nextProps, nextContext) {
if (
shallowEqual(this.props, nextProps) &&
this.client === nextContext.client
) {
return;
}

if (this.client !== nextContext.client) {
this.client = nextContext.client;
}
this.removeQuerySubscription();
this.initializeQueryObservable(nextProps);
this.startQuerySubscription();
this.updateCurrentData();
}

componentWillUnmount() {
this.removeQuerySubscription();
}

render() {
const { children } = this.props;
const result = this.getResult();
return children(result);
}

private initializeQueryObservable(props) {
const {
variables,
pollInterval,
fetchPolicy,
notifyOnNetworkStatusChange,
query,
} = props;

const operation = parser(query);

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

const clientOptions = {
variables,
pollInterval,
query,
fetchPolicy,
notifyOnNetworkStatusChange,
};

this.queryObservable = this.client.watchQuery(clientOptions);
}

private startQuerySubscription() {
this.querySubscription = this.queryObservable.subscribe({
next: this.updateCurrentData,
error: this.updateCurrentData,
});
}

private removeQuerySubscription() {
if (this.querySubscription) {
this.querySubscription.unsubscribe();
}
}

private updateCurrentData() {
this.setState({ result: this.queryObservable.currentResult() });
}

private getResult() {
const { result } = this.state;

const { loading, error, networkStatus, data } = result;

const renderProps = {
data,
loading,
error,
networkStatus,
...observableQueryFields(this.queryObservable),
};

return renderProps;
}
}

export default Query;
1 change: 1 addition & 0 deletions src/browser.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { default as ApolloProvider } from './ApolloProvider';
export { default as Query } from './Query';
export { default as graphql } from './graphql';
export {
MutationOpts,
Expand Down
Loading