From bd83ed5c545bd169852b6f175b2b5eeb51906ad3 Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Tue, 31 Jan 2017 04:26:17 +0100 Subject: [PATCH] [WIP] Remember scroll position on error (#911) * Remember scroll position on error * Added comment + check if lastScroll was set * Remove check for lastAppProps * Use events to make scroll persistence dev-only * Return EventEmitter from next() * Update next-dev.js --- client/index.js | 21 +++++++++++++++++---- client/next-dev.js | 30 ++++++++++++++++++++++++++++-- 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/client/index.js b/client/index.js index 626fee5d0a806..e167f1269beea 100644 --- a/client/index.js +++ b/client/index.js @@ -1,5 +1,6 @@ import { createElement } from 'react' import ReactDOM from 'react-dom' +import { EventEmitter } from 'events' import HeadManager from './head-manager' import { rehydrate } from '../lib/css' import { createRouter } from '../lib/router' @@ -33,13 +34,16 @@ const headManager = new HeadManager() const container = document.getElementById('__next') export default (onError) => { + const emitter = new EventEmitter() if (ids && ids.length) rehydrate(ids) router.subscribe(({ Component, props, err }) => { - render({ Component, props, err }, onError) + render({ Component, props, err, emitter }, onError) }) - render({ Component, props, err }, onError) + render({ Component, props, err, emitter }, onError) + + return emitter } export async function render (props, onError = renderErrorComponent) { @@ -56,7 +60,7 @@ async function renderErrorComponent (err) { await doRender({ Component: ErrorComponent, props, err }) } -async function doRender ({ Component, props, err }) { +async function doRender ({ Component, props, err, emitter }) { if (!props && Component && Component !== ErrorComponent && lastAppProps.Component === ErrorComponent) { @@ -65,10 +69,19 @@ async function doRender ({ Component, props, err }) { props = await loadGetInitialProps(Component, { err, pathname, query }) } + if (emitter) { + emitter.emit('before-reactdom-render', { Component }) + } + Component = Component || lastAppProps.Component props = props || lastAppProps.props const appProps = { Component, props, err, router, headManager } - lastAppProps = appProps ReactDOM.render(createElement(App, appProps), container) + + if (emitter) { + emitter.emit('after-reactdom-render', { Component }) + } + + lastAppProps = appProps } diff --git a/client/next-dev.js b/client/next-dev.js index 2ae0ba06f9bb3..00c1dd93ce751 100644 --- a/client/next-dev.js +++ b/client/next-dev.js @@ -1,4 +1,8 @@ import patch from './patch-react' +import evalScript from '../lib/eval-script' + +const { __NEXT_DATA__: { errorComponent } } = window +const ErrorComponent = evalScript(errorComponent).default // apply patch first patch((err) => { @@ -13,10 +17,32 @@ require('react-hot-loader/patch') const next = window.next = require('./') -next.default(onError) +const emitter = next.default(onError) function onError (err) { // just show the debug screen but don't render ErrorComponent // so that the current component doesn't lose props - next.render({ err }) + next.render({ err, emitter }) } + +let lastScroll + +emitter.on('before-reactdom-render', ({ Component }) => { + // Remember scroll when ErrorComponent is being rendered to later restore it + if (!lastScroll && Component === ErrorComponent) { + const { pageXOffset, pageYOffset } = window + lastScroll = { + x: pageXOffset, + y: pageYOffset + } + } +}) + +emitter.on('after-reactdom-render', ({ Component }) => { + if (lastScroll && Component !== ErrorComponent) { + // Restore scroll after ErrorComponent was replaced with a page component by HMR + const { x, y } = lastScroll + window.scroll(x, y) + lastScroll = null + } +})