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

Commit

Permalink
propagate fetchMore (#123)
Browse files Browse the repository at this point in the history
* propagate fetchMore

* bind the fetchMore method

* update apollo-client dependency and typings

* update apollo-client dependency and typings

* add a test for fetchMore

* fix loading state on refetch queries when data does not change

* use loading instead of loading more

* bump
  • Loading branch information
Slava authored and James Baxley committed Aug 2, 2016
1 parent 35e76f3 commit 4ee77ba
Show file tree
Hide file tree
Showing 24 changed files with 472 additions and 191 deletions.
5 changes: 5 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

Expect active development and potentially significant breaking changes in the `0.x` track. We'll try to be diligent about releasing a `1.0` version in a timely fashion (ideally within 1 or 2 months), so that we can take advantage of SemVer to signify breaking changes from that point on.

### v0.3.20

- Bug: Fixed loading state on refetch more when data doesn't change
- Feature: added fetchMore [#123](https://github.com/apollostack/react-apollo/pull/123)

### v0.3.19

- Bug: Retain compatibility with version 0.3.0 of Apollo Client via a backcompat shim. [#109](https://github.com/apollostack/react-apollo/pull/109)
Expand Down
6 changes: 3 additions & 3 deletions global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
LODASH
*/
declare module 'lodash.isobject' {
import main = require('~lodash/index');
import main = require('lodash');
export = main.isObject;
}

declare module 'lodash.isequal' {
import main = require('~lodash/index');
import main = require('lodash');
export = main.isEqual;
}

Expand All @@ -29,6 +29,6 @@ declare module 'hoist-non-react-statics' {
}

declare module 'lodash.flatten' {
import main = require('~lodash/index');
import main = require('lodash');
export = main.flatten;
}
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-apollo",
"version": "0.3.19",
"version": "0.3.20",
"description": "React data container for Apollo Client",
"main": "index.js",
"scripts": {
Expand Down Expand Up @@ -37,7 +37,7 @@
"apollo-client": "^0.1.0 || ^0.2.0 || ^0.3.0 || ^0.4.0"
},
"devDependencies": {
"apollo-client": "^0.4.0",
"apollo-client": "^0.4.9",
"browserify": "^13.0.0",
"chai": "^3.5.0",
"chai-as-promised": "^5.2.0",
Expand Down
58 changes: 49 additions & 9 deletions src/connect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import ApolloClient, {

import {
ObservableQuery,
} from 'apollo-client/QueryManager';
} from 'apollo-client/ObservableQuery';

import {
Subscription,
Expand Down Expand Up @@ -343,7 +343,9 @@ export default function connect(opts?: ConnectOptions) {
// has been recieved
let refetch,
startPolling,
stopPolling;
stopPolling,
fetchMore,
oldData = {};

// since we don't have the query id, we can manually handle
// a lifecyle event for loading if this query is refetched
Expand All @@ -361,26 +363,59 @@ export default function connect(opts?: ConnectOptions) {
this.forceRenderChildren();
}

let previousRequest = assign({}, oldData);
return refetchMethod(...args)
.then((result) => {
const { data } = result;

if (isEqual(data, previousRequest)) {
this.data[dataKey] = assign(this.data[dataKey], {
loading: false,
});
this.hasQueryDataChanged = true;

if (this.hasMounted) {
this.forceRenderChildren();
}
}
previousRequest = assign({}, data);
return result;
});
};
};

return refetchMethod(...args);
const createBoundFetchMore = (dataKey, fetchMoreMethod) => {
return (...args) => {
this.data[dataKey] = assign(this.data[dataKey], {
loading: true,
fetchMore,
});

this.hasQueryDataChanged = true;

if (this.hasMounted) {
this.forceRenderChildren();
}

return fetchMoreMethod(...args);
};
};

let oldData = {};
const forceRender = ({ errors, data = oldData }: any) => {
const resultKeyConflict: boolean = (
'errors' in data ||
'loading' in data ||
'refetch' in data ||
'startPolling' in data ||
'stopPolling' in data
'stopPolling' in data ||
'fetchMore' in data
);

invariant(!resultKeyConflict,
`the result of the '${key}' query contains keys that ` +
`conflict with the return object. 'errors', 'loading', ` +
`'startPolling', 'stopPolling', and 'refetch' cannot be ` +
`returned keys`
`'startPolling', 'stopPolling', 'refetch', and 'fetchMore' ` +
`cannot be returned keys`
);

// only rerender child component if data has changed
Expand All @@ -394,10 +429,10 @@ export default function connect(opts?: ConnectOptions) {

this.data[key] = assign({
loading: false,
errors,
refetch, // copy over refetch method
startPolling,
stopPolling,
fetchMore,
}, data);

if (this.hasMounted) {
Expand All @@ -420,11 +455,14 @@ export default function connect(opts?: ConnectOptions) {
(this.querySubscriptions[key] as any).startPolling;
stopPolling = this.queryObservables[key].stopPolling ||
(this.querySubscriptions[key] as any).stopPolling;
fetchMore = createBoundFetchMore(key,
this.queryObservables[key].fetchMore);

this.data[key] = assign(this.data[key], {
refetch,
startPolling,
stopPolling,
fetchMore,
});
}

Expand Down Expand Up @@ -466,12 +504,14 @@ export default function connect(opts?: ConnectOptions) {
const resultKeyConflict: boolean = (
'errors' in data ||
'loading' in data ||
'fetchMore' in data ||
'refetch' in data
);

invariant(!resultKeyConflict,
`the result of the '${key}' mutation contains keys that ` +
`conflict with the return object. 'errors', 'loading', and 'refetch' cannot be ` +
`conflict with the return object. 'errors', 'loading', ` +
`fetchMore' and 'refetch' cannot be ` +
`returned keys`
);

Expand Down
96 changes: 96 additions & 0 deletions test/client/connect/queries.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1742,6 +1742,102 @@ describe('queries', () => {
);
});

it('should update props after fetching more with fetchMore', (done) => {
const store = createStore(() => ({ }));

const query = gql`
query people($skip: Int) {
allPeople(first: 1, skip: $skip) {
people {
name
}
}
}
`;

const variables = { skip: 0 };
const variablesMore = { skip: 1 };

const data = {
allPeople: {
people: [
{
name: 'Luke Skywalker',
},
],
},
};

const dataMore = {
allPeople: {
people: [
{
name: 'Anakin Skywalker',
},
],
},
};

const networkInterface = mockNetworkInterface({
request: { query, variables },
result: { data },
}, {
request: { query, variables: variablesMore },
result: { data: dataMore },
});

const client = new ApolloClient({
networkInterface,
});

function mapQueriesToProps() {
return {
luke: { query, variables },
};
};

let iter = 0;

@connect({ mapQueriesToProps })
class Container extends React.Component<any, any> {
componentDidUpdate(prevProps) {
if (iter === 0) {
expect(prevProps.luke.loading).to.be.true;
expect(this.props.luke.allPeople).to.deep.equal(data.allPeople);
this.props.luke.fetchMore({
variables: variablesMore,
updateQuery: (prev, { fetchMoreResult }) => {
return {
allPeople: {
people: prev.allPeople.people.concat(fetchMoreResult.data.allPeople.people),
},
};
},
});
} else if (iter === 1) {
expect(prevProps.luke.loading).to.be.true;
expect(this.props.luke.allPeople).to.deep.equal(data.allPeople);
} else if (iter === 2) {
expect(this.props.luke.loading).to.be.false;
expect(this.props.luke.allPeople.people).to.deep.equal(data.allPeople.people.concat(dataMore.allPeople.people));
done();
} else {
throw new Error('should not reach this statement');
}
iter++;
}
render() {
return <Passthrough {...this.props} />;
}
};

mount(
<ProviderMock store={store} client={client}>
<Container />
</ProviderMock>
);
});

it('should prefill any data already in the store', (done) => {

const query = gql`
Expand Down
42 changes: 31 additions & 11 deletions test/server/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,36 @@ describe('SSR', () => {
return <div>{data.loading ? 'loading' : 'loaded'}</div>;
};

const query = gql`
query App {
currentUser {
firstName
}
}
`;

const data1 = {
currentUser: {
firstName: 'James',
},
};

const networkInterface = mockNetworkInterface(
{
request: { query },
result: { data: data1 },
delay: 50,
}
);

const client = new ApolloClient({
networkInterface,
});

const WrappedElement = connect({
mapQueriesToProps: () => ({
data: {
query: gql`
query Feed {
currentUser {
login
}
}
`,
query,
},
}),
})(Element);
Expand Down Expand Up @@ -135,7 +155,7 @@ describe('SSR', () => {
getDataFromTree(app)
.then(({ initialState }) => {
expect(initialState.apollo.data).to.exist;
expect(initialState.apollo.data['ROOT_QUERY.currentUser']).to.exist;
expect(initialState.apollo.data['$ROOT_QUERY.currentUser']).to.exist;
done();
});
});
Expand Down Expand Up @@ -183,7 +203,7 @@ describe('SSR', () => {
getDataFromTree(app)
.then(({ initialState }) => {
expect(initialState.apollo.data).to.exist;
expect(initialState.apollo.data['ROOT_QUERY.currentUser({"ctrn":1})']).to.exist;
expect(initialState.apollo.data['$ROOT_QUERY.currentUser({"ctrn":1})']).to.exist;
done();
})
.catch(done);
Expand Down Expand Up @@ -233,7 +253,7 @@ describe('SSR', () => {
getDataFromTree(app)
.then(({ initialState }) => {
expect(initialState.apollo.data).to.exist;
expect(initialState.apollo.data['ROOT_QUERY.currentUser({"ctrn":0})']).to.exist;
expect(initialState.apollo.data['$ROOT_QUERY.currentUser({"ctrn":0})']).to.exist;
done();
})
.catch(done);
Expand Down Expand Up @@ -284,7 +304,7 @@ describe('SSR', () => {
getDataFromTree(app)
.then(({ initialState }) => {
expect(initialState.apollo.data).to.exist;
expect(initialState.apollo.data['ROOT_QUERY.currentUser']).to.not.exist;
expect(initialState.apollo.data['$ROOT_QUERY.currentUser']).to.not.exist;
done();
});
});
Expand Down
2 changes: 1 addition & 1 deletion typings/globals/enzyme/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -403,4 +403,4 @@ declare module "enzyme" {
export function describeWithDOM(description: String, fn: Function): void;

export function spyLifecycle(component: typeof Component): void;
}
}
2 changes: 1 addition & 1 deletion typings/globals/es6-promise/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,4 @@ declare module 'es6-promise' {
export var Promise: typeof foo;
}
export = rsvp;
}
}
2 changes: 1 addition & 1 deletion typings/globals/graphql/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -936,4 +936,4 @@ declare module "graphql" {
directives?: Array<GraphQLDirective>;
}

}
}
2 changes: 1 addition & 1 deletion typings/globals/isomorphic-fetch/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,4 +113,4 @@ declare module "isomorphic-fetch" {
export default IFetchStatic;
}

declare var fetch: IFetchStatic;
declare var fetch: IFetchStatic;
2 changes: 1 addition & 1 deletion typings/globals/mocha/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,4 +230,4 @@ declare namespace Mocha {

declare module "mocha" {
export = Mocha;
}
}
Loading

0 comments on commit 4ee77ba

Please sign in to comment.