From 26e30f21960aa2c08728a35023ab51ef45c4676c Mon Sep 17 00:00:00 2001 From: James Baxley Date: Wed, 29 Jun 2016 20:59:31 -0400 Subject: [PATCH] Error catching (#89) * finally fix caught errors * only render the error once * version bump --- Changelog.md | 4 +++ package.json | 2 +- src/connect.tsx | 36 +++++++++++++++++--- test/client/connect/queries.tsx | 58 +++++++++++++++++++++++++++++++++ 4 files changed, 94 insertions(+), 6 deletions(-) diff --git a/Changelog.md b/Changelog.md index f8fc22ae23..a747e88040 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,10 @@ 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.13 + +Bug: fixed issue causing errors to be passed to apollo-client [#89](https://github.com/apollostack/react-apollo/pull/89) + ### v0.3.11/12 Bug: fixed overrendering of components on redux state changes diff --git a/package.json b/package.json index ecd30aa5ce..4231845fd1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-apollo", - "version": "0.3.12", + "version": "0.3.13", "description": "React data container for Apollo Client", "main": "index.js", "scripts": { diff --git a/src/connect.tsx b/src/connect.tsx index 1caf15035a..195f3a243b 100644 --- a/src/connect.tsx +++ b/src/connect.tsx @@ -137,6 +137,8 @@ export default function connect(opts?: ConnectOptions) { private hasQueryDataChanged: boolean; private hasMutationDataChanged: boolean; private hasOwnStateChanged: boolean; + private childRenderError: any = null; + private isRenderingError: boolean = false; // the element to render private renderedElement: any; @@ -203,6 +205,25 @@ export default function connect(opts?: ConnectOptions) { this.hasMounted = false; } + forceRenderChildren() { + const { isRenderingError } = this; + // ensure setState throws an error in the render + // to prevent it from going to apollo-client as a + // network error + try { + // update state to latest of redux store + this.setState(this.store.getState()); + } catch (e) { + // save for the next render + this.childRenderError = e; + this.isRenderingError = true; + if (!isRenderingError) { + this.forceUpdate(); + } + } + + } + bindStoreUpdates(): void { const { store } = this; const { reduxRootKey } = this.client; @@ -317,7 +338,7 @@ export default function connect(opts?: ConnectOptions) { if (this.hasMounted) { // update state to latest of redux store - this.setState(this.store.getState()); + this.forceRenderChildren(); } @@ -360,8 +381,7 @@ export default function connect(opts?: ConnectOptions) { }, data); if (this.hasMounted) { - // update state to latest of redux store - this.setState(this.store.getState()); + this.forceRenderChildren(); } }; @@ -438,7 +458,7 @@ export default function connect(opts?: ConnectOptions) { if (this.hasMounted) { // update state to latest of redux store // this forces a render of children - this.setState(store.getState()); + this.forceRenderChildren(); } return { @@ -467,7 +487,7 @@ export default function connect(opts?: ConnectOptions) { if (this.hasMounted) { // update state to latest of redux store // this forces a render of children - this.setState(store.getState()); + this.forceRenderChildren(); } resolve(); @@ -487,12 +507,18 @@ export default function connect(opts?: ConnectOptions) { hasOwnStateChanged, hasQueryDataChanged, hasMutationDataChanged, + childRenderError, renderedElement, mutations, props, data, } = this; + this.childRenderError = null; + if (childRenderError) { + throw childRenderError; + } + this.haveOwnPropsChanged = false; this.hasOwnStateChanged = false; this.hasQueryDataChanged = false; diff --git a/test/client/connect/queries.tsx b/test/client/connect/queries.tsx index 2f57eb4b6e..a502b7c6b1 100644 --- a/test/client/connect/queries.tsx +++ b/test/client/connect/queries.tsx @@ -1930,4 +1930,62 @@ describe('queries', () => { done(); }, 50); }); + it('should not swallow errors', (done) => { + const query = gql` + query sample { + viewer { + name + } + } + `; + + const data = { + viewer: { name: 'James' }, + }; + + const networkInterface = mockNetworkInterface({ + request: { query }, + result: { data }, + delay: 10, + }); + + const client = new ApolloClient({ + networkInterface, + }); + + let count = 0; + function BadComponent(props) { + count++; + if (props.data.loading) { + return null; + } + + if (props.data.errors) { + done(props.data.errors); + return null; + } else if (count === 2) { + done(); + return null; + } + + const name = props.data.typo.name; + return

Hi {name}

; + } + + function mapQueriesToProps() { + return { + data: { query }, + }; + } + + const BadContainer = connect({ + mapQueriesToProps, + })(BadComponent); + + mount( + + + + ); + }); });