Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(gatsby): refresh browser when receiving update and runtime errored #27467

Merged
merged 4 commits into from
Oct 16, 2020

Conversation

pieh
Copy link
Contributor

@pieh pieh commented Oct 15, 2020

Description

This adds crude recovery mechanism: once runtime error was found attach listeners to refresh the browser on either webpack update or data update.

Recently we fixed queueing of query running when editing query text ( #27408 ). However in runtime when users update query and component at the same time, runtime can get in error state because webpack and data updates are not applied at the same time (meaning that we can have either older component or older content for some period of time that can lead to runtime errors). This change allow to recover from situation like that.

--edit:
Please note that the change here is not just for case described above and it's rather generic. Above we ideally fix at some point by synchronising applying updates from webpack HMR and query results from websocket to avoid even getting in runtime errors scenarios, but this does cover more cases than just that ... and with some quirks it does "fix" that problem too (at least users don't have to manually refresh browser tabs)

This also updates react-hot-loader which does change the overlay look (and looks like overlay used by fast-refresh, because it actually is) - this might not be needed for this PR and possibly I can revert that part (I started with bumping overlay thinking I would be able to make use of dismissRuntimeErrors that was exposed in newer version of overlay, but ended up not using it):
testing error overlay and ability to automatically recover from runtime errors -- displays error with overlay on runtime errors (22)
testing error overlay and ability to automatically recover from webpack compile errors -- displays error with overlay on compilation errors (7)

---EDIT:
Above screenshots are from "night mode", based on prefers-color-scheme: dark media feature (that react-error-overlay now supports). For users with preference set to light, it will keep looking as it used to.

I added few basic tests for error overlay interactions: (make sure we start with working site -> break something intentionally -> assert error overlay -> fix the problem -> assert that browser automatically recovered)

@gatsbot gatsbot bot added the status: triage needed Issue or pull request that need to be triaged and assigned to a reviewer label Oct 15, 2020
@pieh pieh added topic: core Relates to Gatsby's core (e.g. page loading, reporter, state machine) and removed status: triage needed Issue or pull request that need to be triaged and assigned to a reviewer labels Oct 15, 2020
@pieh pieh force-pushed the retry-rendering-on-updates branch from 3957bea to 907b180 Compare October 15, 2020 13:42
@pieh pieh marked this pull request as ready for review October 15, 2020 17:40

describe(`testing error overlay and ability to automatically recover from webpack compile errors`, () => {
it(`displays content initially (no errors yet)`, () => {
cy.visit(`/error-handling/compile-error/`).waitForRouteChange()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important part in those tests that we have just single initial cy.visit and we don't interact with page in any way (don't manually refresh, don't click anything, we only do changes to either source code or content and assert that fixing the problem cause runtime to work again automatically - even if we do refresh browser ourselves, user doesn't have to).

Comment on lines +17 to +54
let registeredReloadListeners = false
function onError() {
if (registeredReloadListeners) {
return
}

// Inspired by `react-dev-utils` HMR client:
// If there was unhandled error, reload browser
// on next HMR update
module.hot.addStatusHandler(status => {
if (status === `apply` || status === `idle`) {
window.location.reload()
}
})

// Additionally in Gatsby case query result updates can cause
// runtime error and also fix them, so reload on data updates
// as well
___emitter.on(`pageQueryResult`, () => {
window.location.reload()
})
___emitter.on(`staticQueryResult`, () => {
window.location.reload()
})

registeredReloadListeners = true
}
ReactErrorOverlay.startReportingRuntimeErrors({
onError: () => {},
onError,
filename: `/commons.js`,
})

// ReactErrorOverlay `onError` handler is triggered pretty late
// so we attach same error/unhandledrejection as ReactErrorOverlay
// to be able to detect runtime error and setup listeners faster
window.addEventListener(`error`, onError)
window.addEventListener(`unhandledrejection`, onError)

Copy link
Contributor Author

@pieh pieh Oct 15, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is actual change (lot of test boilerplate in this PR, so I do want to highlight code change, so it's not lost)

Copy link
Contributor

@vladar vladar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me, thanks! 👍

@pieh pieh merged commit f227e85 into master Oct 16, 2020
@delete-merged-branch delete-merged-branch bot deleted the retry-rendering-on-updates branch October 16, 2020 11:04
@jablonski
Copy link

This change seems to break existing code. With Gatsby 2.24.80 our Dev-Site is constantly reloading, while it works fine with 2.24.79. We have currently some unfixed ESLint-problems (no errors, just warnings) in the browser console, which should not trigger a reload.

But maybe the problem arises from this line:

32:6 warning React Hook useEffect has a missing dependency: '...'. Either include it or remove the dependency array react-hooks/exhaustive-deps
✖ 1 problem (0 errors, 1 warning)
0 errors and 1 warning potentially fixable with the --fix option.

@vladar
Copy link
Contributor

vladar commented Oct 19, 2020

Hi @jablonski !

Thanks for reporting. Can you please open a new issue with a reproduction?

@jablonski
Copy link

I've tracked down the problem... we are including an external consent service script into our Gatsby site which works on 127.0.0.1, but throws an error on localhost. This error triggers the constant reload.

Maybe this new behaviour should be configurable? I can image lots of users who will encounter Javascript errors related to third party scripts which cannot be controlled under all circumstances (think of different environments / states of the application etc.pp.).

@pieh
Copy link
Contributor Author

pieh commented Oct 19, 2020

Maybe this new behaviour should be configurable? I can image lots of users who will encounter Javascript errors related to third party scripts which cannot be controlled under all circumstances (think of different environments / states of the application etc.pp.).

How does this 3rd party script get's added? Is it part of react component tree or completely separate? I could possibly change from all runtime errors to using React error boundaries to catch only react render runtime errors and not react to 3rd party scripts (as long as they are not party of react tree)

@jablonski
Copy link

Actually this script is loaded via Helmet within our generic page template:

      <script
        type="application/javascript"
        src="https://app.usercentrics.eu/latest/main.js"
        id="..."
      ></script>

@pieh
Copy link
Contributor Author

pieh commented Oct 19, 2020

Do you manually call functions from that script or it automatically runs what it needs to?

@jablonski
Copy link

The problem arises by just loading the script...

We have some onClick-Handlers which trigger functions from this script later on:

<a onClick={() => typeof window === "object" && window["usercentrics"] && window["usercentrics"].updateConsents([ { "templateId": ucTemplateId, "status": true }, ]) } > Activate Google Maps </a>

@pieh
Copy link
Contributor Author

pieh commented Oct 19, 2020

I did reproduce the problem with just adding external script (via helmet) that just throws.

From my research:
react-error-overlay doesn't show the overlay for this, because https://github.com/facebook/create-react-app/blob/master/packages/react-error-overlay/src/utils/getStackFrames.js#L14-L46 returns null for those and this bail early before showing overlay.

What I can try doing here is to use react error boundary instead of catching all runtime errors - then errors from 3rd party scripts wouldn't trigger . I've seen some weird behaviour with react-hot-loader tho, so it might not work - TBD

pieh added a commit that referenced this pull request Nov 13, 2020
gatsbybot pushed a commit that referenced this pull request Nov 13, 2020
vladar pushed a commit that referenced this pull request Nov 13, 2020
…e errored (#27467)" (#28034)

This reverts commit f227e85.

(cherry picked from commit 076b59f)
vladar added a commit that referenced this pull request Nov 13, 2020
…e errored (#27467)" (#28034) (#28038)

This reverts commit f227e85.

(cherry picked from commit 076b59f)
LekoArts pushed a commit that referenced this pull request Mar 10, 2021
#27467)

* fix(gatsby): refresh browser when receiving update and runtime errored

* add query validation error test

* add test cases for changing content causing and fixing runtime errors

* adjust test names

(cherry picked from commit f227e85)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
topic: core Relates to Gatsby's core (e.g. page loading, reporter, state machine)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants