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

Ensure that SSR completes when the GraphQL server throws errors. #488

Merged
merged 9 commits into from
Mar 17, 2017
1 change: 1 addition & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Expect active development and potentially significant breaking changes in the `0

### vNext
- Fix bug where `options` was mutated causing variables to not update appropriately. [PR #537](https://github.com/apollographql/react-apollo/pull/537)
- Make sure that all queries resolve or reject if an error was thrown when server side rendering. [PR #488](https://github.com/apollographql/react-apollo/pull/488)

### 1.0.0-rc.1
- Update dependency to Apollo Client 1.0.0-rc.1 [PR #520](https://github.com/apollographql/react-apollo/pull/520)
Expand Down
20 changes: 18 additions & 2 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,12 +125,28 @@ export function getDataFromTree(rootElement, rootContext: any = {}, fetchRoot: b
// no queries found, nothing to do
if (!queries.length) return Promise.resolve();

const errors = [];
// wait on each query that we found, re-rendering the subtree when it's done
const mappedQueries = queries.map(({ query, element, context }) => {
// we've just grabbed the query for element, so don't try and get it again
return query.then(_ => getDataFromTree(element, context, false));
return (
query
.then(_ => getDataFromTree(element, context, false))
.catch(e => errors.push(e))
);
});

// Run all queries. If there are errors, still wait for all queries to execute
// so the caller can ignore them if they wish. See https://github.com/apollographql/react-apollo/pull/488#issuecomment-284415525
return Promise.all(mappedQueries).then(_ => {
if (errors.length > 0) {
const error = errors.length === 1
? errors[0]
: new Error(`${errors.length} errors were thrown when executing your GraphQL queries.`);
error.queryErrors = errors;
throw error;
}
});
return Promise.all(mappedQueries).then(_ => null);
}

export function renderToStringWithData(component) {
Expand Down
28 changes: 28 additions & 0 deletions test/react-web/server/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,34 @@ describe('SSR', () => {
;
});

it('should handle errors thrown by queries', () => {
const query = gql`{ currentUser { firstName } }`;
const networkInterface = mockNetworkInterface(
{ request: { query }, error: new Error('Failed to fetch'), delay: 50 }
);
const apolloClient = new ApolloClient({ networkInterface, addTypename: false });

const WrappedElement = graphql(query)(({ data }) => (
<div>{data.loading ? 'loading' : data.error}</div>
));

const Page = () => (<div><span>Hi</span><div><WrappedElement /></div></div>);

const app = (<ApolloProvider client={apolloClient}><Page/></ApolloProvider>);

return getDataFromTree(app)
.catch((e) => {
expect(e).toBeTruthy();
expect(e.queryErrors.length).toEqual(1);

// But we can still render the app if we want to
const markup = ReactDOM.renderToString(app);
// It renders in a loading state as errored query isn't shared between
// the query fetching run and the rendering run.
expect(markup).toMatch(/loading/);
});
});

it('should correctly skip queries (deprecated)', () => {

const query = gql`{ currentUser { firstName } }`;
Expand Down