From 83735299e4b998a103b1629e436ed8af89cf1066 Mon Sep 17 00:00:00 2001 From: Blaine Kasten Date: Wed, 20 May 2020 14:08:55 -0500 Subject: [PATCH 01/14] fix(gatsby-react-router-scroll): Major improvements to scroll handling --- .../gatsby-react-router-scroll/package.json | 4 +- .../src/ScrollBehaviorContext.js | 110 +++++++++--------- .../src/ScrollContainer.js | 83 ------------- .../gatsby-react-router-scroll/src/index.js | 2 - yarn.lock | 55 +++++---- 5 files changed, 84 insertions(+), 170 deletions(-) delete mode 100644 packages/gatsby-react-router-scroll/src/ScrollContainer.js diff --git a/packages/gatsby-react-router-scroll/package.json b/packages/gatsby-react-router-scroll/package.json index 6a3f1ca9edf65..066bcc16503c7 100644 --- a/packages/gatsby-react-router-scroll/package.json +++ b/packages/gatsby-react-router-scroll/package.json @@ -7,9 +7,7 @@ "url": "https://github.com/gatsbyjs/gatsby/issues" }, "dependencies": { - "@babel/runtime": "^7.9.6", - "scroll-behavior": "^0.9.12", - "warning": "^3.0.0" + "@babel/runtime": "^7.9.6" }, "devDependencies": { "@babel/cli": "^7.8.4", diff --git a/packages/gatsby-react-router-scroll/src/ScrollBehaviorContext.js b/packages/gatsby-react-router-scroll/src/ScrollBehaviorContext.js index 8cb69e53fbd0b..a8f9ae0807ede 100644 --- a/packages/gatsby-react-router-scroll/src/ScrollBehaviorContext.js +++ b/packages/gatsby-react-router-scroll/src/ScrollBehaviorContext.js @@ -1,51 +1,64 @@ import React from "react" -import ScrollBehavior from "scroll-behavior" import PropTypes from "prop-types" -import { globalHistory as history } from "@reach/router/lib/history" import SessionStorage from "./StateStorage" -export const ScrollBehaviorContext = React.createContext() - -const propTypes = { - shouldUpdateScroll: PropTypes.func, - children: PropTypes.element.isRequired, - location: PropTypes.object.isRequired, -} - -class ScrollContext extends React.Component { +export default class ScrollContext extends React.Component { constructor(props, context) { super(props, context) - this.scrollBehavior = new ScrollBehavior({ - addTransitionHook: history.listen, - stateStorage: new SessionStorage(), - getCurrentLocation: () => this.props.location, - shouldUpdateScroll: this.shouldUpdateScroll, - }) + this._stateStorage = new SessionStorage() } - componentDidUpdate(prevProps) { - const { location } = this.props - const prevLocation = prevProps.location + scrollListener = () => { + const { key } = this.props.location - if (location === prevLocation) { - return - } + this._stateStorage.save(this.props.location, key, window.scrollY) + } - const prevRouterProps = { - location: prevProps.location, - } + componentDidMount() { + window.addEventListener(`scroll`, this.scrollListener) - this.scrollBehavior.updateScroll(prevRouterProps, { history, location }) + const scrollPosition = this._stateStorage.read( + this.props.location, + this.props.location.key + ) + if (scrollPosition) { + this.windowScroll(scrollPosition) + } else if (this.props.location.hash) { + this.scrollToHash(decodeURI(this.props.location.hash)) + } } componentWillUnmount() { - this.scrollBehavior.stop() + window.removeEventListener(`scroll`, this.scrollListener) } - getRouterProps() { - const { location } = this.props - return { location, history } + componentDidUpdate(prevProps) { + const { hash } = this.props.location + + const scrollPosition = this._stateStorage.read( + this.props.location, + this.props.location.key + ) + if (scrollPosition) { + this.windowScroll(scrollPosition, prevProps) + } else if (hash) { + this.scrollToHash(decodeURI(hash), prevProps) + } + } + + windowScroll = (position, prevProps) => { + if (this.shouldUpdateScroll(prevProps, this.props)) { + window.scroll(0, position) + } + } + + scrollToHash = (hash, prevProps) => { + const node = document.querySelector(hash) + + if (node && this.shouldUpdateScroll(prevProps, this.props)) { + node.scrollIntoView() + } } shouldUpdateScroll = (prevRouterProps, routerProps) => { @@ -54,36 +67,17 @@ class ScrollContext extends React.Component { return true } - // Hack to allow accessing scrollBehavior._stateStorage. - return shouldUpdateScroll.call( - this.scrollBehavior, - prevRouterProps, - routerProps - ) - } - - registerElement = (key, element, shouldUpdateScroll) => { - this.scrollBehavior.registerElement( - key, - element, - shouldUpdateScroll, - this.getRouterProps() - ) - } - - unregisterElement = key => { - this.scrollBehavior.unregisterElement(key) + // Hack to allow accessing this._stateStorage. + return shouldUpdateScroll.call(this, prevRouterProps, routerProps) } render() { - return ( - - {React.Children.only(this.props.children)} - - ) + return this.props.children } } -ScrollContext.propTypes = propTypes - -export default ScrollContext +ScrollContext.propTypes = { + shouldUpdateScroll: PropTypes.func, + children: PropTypes.element.isRequired, + location: PropTypes.object.isRequired, +} diff --git a/packages/gatsby-react-router-scroll/src/ScrollContainer.js b/packages/gatsby-react-router-scroll/src/ScrollContainer.js deleted file mode 100644 index 1373b8c41791c..0000000000000 --- a/packages/gatsby-react-router-scroll/src/ScrollContainer.js +++ /dev/null @@ -1,83 +0,0 @@ -import React from "react" -import ReactDOM from "react-dom" -import warning from "warning" -import PropTypes from "prop-types" -import { ScrollBehaviorContext } from "./ScrollBehaviorContext" - -const propTypes = { - scrollKey: PropTypes.string.isRequired, - shouldUpdateScroll: PropTypes.func, - children: PropTypes.element.isRequired, -} - -class ScrollContainer extends React.Component { - constructor(props) { - super(props) - - // We don't re-register if the scroll key changes, so make sure we - // unregister with the initial scroll key just in case the user changes it. - this.scrollKey = props.scrollKey - } - - componentDidMount() { - this.props.context.registerElement( - this.props.scrollKey, - ReactDOM.findDOMNode(this), // eslint-disable-line react/no-find-dom-node - this.shouldUpdateScroll - ) - - // Only keep around the current DOM node in development, as this is only - // for emitting the appropriate warning. - if (process.env.NODE_ENV !== `production`) { - this.domNode = ReactDOM.findDOMNode(this) // eslint-disable-line react/no-find-dom-node - } - } - - componentDidUpdate(prevProps) { - warning( - prevProps.scrollKey === this.props.scrollKey, - ` does not support changing scrollKey.` - ) - if (process.env.NODE_ENV !== `production`) { - const prevDomNode = this.domNode - this.domNode = ReactDOM.findDOMNode(this) // eslint-disable-line react/no-find-dom-node - - warning( - this.domNode === prevDomNode, - ` does not support changing DOM node.` - ) - } - } - - componentWillUnmount() { - this.props.context.unregisterElement(this.scrollKey) - } - - shouldUpdateScroll = (prevRouterProps, routerProps) => { - const { shouldUpdateScroll } = this.props - if (!shouldUpdateScroll) { - return true - } - - // Hack to allow accessing scrollBehavior._stateStorage. - return shouldUpdateScroll.call( - this.props.context.scrollBehavior, - prevRouterProps, - routerProps - ) - } - - render() { - return this.props.children - } -} - -const ScrollContainerConsumer = props => ( - - {context => } - -) - -ScrollContainerConsumer.propTypes = propTypes - -export default ScrollContainerConsumer diff --git a/packages/gatsby-react-router-scroll/src/index.js b/packages/gatsby-react-router-scroll/src/index.js index d0f30139c3da6..66d9077fb7495 100644 --- a/packages/gatsby-react-router-scroll/src/index.js +++ b/packages/gatsby-react-router-scroll/src/index.js @@ -1,4 +1,2 @@ import ScrollBehaviorContext from "./ScrollBehaviorContext" -import ScrollContainer from "./ScrollContainer" -exports.ScrollContainer = ScrollContainer exports.ScrollContext = ScrollBehaviorContext diff --git a/yarn.lock b/yarn.lock index e962dcffea62d..3c19d7566c281 100644 --- a/yarn.lock +++ b/yarn.lock @@ -116,7 +116,7 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/core@^7.6.6", "@babel/core@^7.8.7", "@babel/core@^7.9.6": +"@babel/core@^7.8.7", "@babel/core@^7.9.6": version "7.9.6" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.9.6.tgz#d9aa1f580abf3b2286ef40b6904d390904c63376" integrity sha512-nD3deLvbsApbHAHttzIssYqgb883yU/d9roe4RZymBCDaZryMJDbptVpEpeQuRh4BJ+SYI8le9YGxKvFEvl1Wg== @@ -2015,13 +2015,6 @@ dependencies: regenerator-runtime "^0.13.2" -"@babel/runtime@^7.1.2", "@babel/runtime@^7.7.4", "@babel/runtime@^7.8.4": - version "7.8.7" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.8.7.tgz#8fefce9802db54881ba59f90bb28719b4996324d" - integrity sha512-+AATMUFppJDw6aiR5NVPHqIQBlV/Pj8wY/EZH+lmvRdUo9xBaz/rF3alAwFJQavvKfeOlPE7oaaDHVbcySbCsg== - dependencies: - regenerator-runtime "^0.13.4" - "@babel/runtime@^7.6.3": version "7.7.4" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.7.4.tgz#b23a856751e4bf099262f867767889c0e3fe175b" @@ -2036,20 +2029,27 @@ dependencies: regenerator-runtime "^0.13.2" -"@babel/runtime@^7.9.2": - version "7.9.2" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.2.tgz#d90df0583a3a252f09aaa619665367bae518db06" - integrity sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q== +"@babel/runtime@^7.7.4", "@babel/runtime@^7.8.4": + version "7.8.7" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.8.7.tgz#8fefce9802db54881ba59f90bb28719b4996324d" + integrity sha512-+AATMUFppJDw6aiR5NVPHqIQBlV/Pj8wY/EZH+lmvRdUo9xBaz/rF3alAwFJQavvKfeOlPE7oaaDHVbcySbCsg== dependencies: regenerator-runtime "^0.13.4" -"@babel/runtime@^7.9.6": +"@babel/runtime@^7.8.7", "@babel/runtime@^7.9.6": version "7.9.6" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.6.tgz#a9102eb5cadedf3f31d08a9ecf294af7827ea29f" integrity sha512-64AF1xY3OAkFHqOb9s4jpgk1Mm5vDZ4L3acHvAml+53nO1XbXLuDodsVpO4OIUsmemlUHMxNdYMNJmsvOwLrvQ== dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.9.2": + version "7.9.2" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.2.tgz#d90df0583a3a252f09aaa619665367bae518db06" + integrity sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/standalone@^7.9.6": version "7.9.6" resolved "https://registry.yarnpkg.com/@babel/standalone/-/standalone-7.9.6.tgz#7a5f82c6fa29959b12f708213be6de8ec0b79338" @@ -8551,7 +8551,7 @@ cssstyle@^2.2.0: dependencies: cssom "~0.3.6" -csstype@^2.2.0: +csstype@^2.2.0, csstype@^2.6.7: version "2.6.10" resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.10.tgz#e63af50e66d7c266edb6b32909cfd0aabe03928b" integrity sha512-D34BqZU4cIlMCY93rZHbrq9pjTAQJ3U8S8rfBqjwHxkGPThWFjzZDQpgMJY0QViLxth6ZKYiwFBo14RdN44U/w== @@ -9331,12 +9331,13 @@ dom-converter@~0.1: dependencies: utila "~0.3" -dom-helpers@^3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.4.0.tgz#e9b369700f959f62ecde5a6babde4bccd9169af8" - integrity sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA== +dom-helpers@^5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.1.4.tgz#4609680ab5c79a45f2531441f1949b79d6587f4b" + integrity sha512-TjMyeVUvNEnOnhzs6uAn9Ya47GmMo3qq7m+Lr/3ON0Rs5kHvb8I+SQYjLUSYn7qhEm0QjW0yrBkvz9yOrwwz1A== dependencies: - "@babel/runtime" "^7.1.2" + "@babel/runtime" "^7.8.7" + csstype "^2.6.7" dom-serializer@0, dom-serializer@~0.1.0, dom-serializer@~0.1.1: version "0.1.1" @@ -18369,6 +18370,11 @@ package-json@^6.3.0: registry-url "^5.0.0" semver "^6.2.0" +page-lifecycle@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/page-lifecycle/-/page-lifecycle-0.1.2.tgz#f17a083c082bd5ababddd77f1025a4b1c8808012" + integrity sha512-+3uccYgL0CXG0KSXRxZi4uc2E6mqFWV5HqiJJgcnaJCiS0LqiuJ4vB420N21NFuLvuvLB4Jr5drgQ2NXAXF9Iw== + pako@^0.2.5: version "0.2.9" resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75" @@ -22270,13 +22276,14 @@ schemes@^1.0.1: dependencies: extend "^3.0.0" -scroll-behavior@^0.9.12: - version "0.9.12" - resolved "https://registry.yarnpkg.com/scroll-behavior/-/scroll-behavior-0.9.12.tgz#1c22d273ec4ce6cd4714a443fead50227da9424c" - integrity sha512-18sirtyq1P/VsBX6O/vgw20Np+ngduFXEMO4/NDFXabdOKBL2kjPVUpz1y0+jm99EWwFJafxf5/tCyMeXt9Xyg== +scroll-behavior@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/scroll-behavior/-/scroll-behavior-0.11.0.tgz#fff2765b6007341b80a04678fcd314e54d5b03ea" + integrity sha512-wQvNs3Q1TRvEkkwrFd/BkIL+dA4PYQl55/FUlmtjgz63/FtbnyR6MkLyRmjK0Rg3LCZCr0jORsFfMLkeNYdFuA== dependencies: - dom-helpers "^3.4.0" + dom-helpers "^5.1.4" invariant "^2.2.4" + page-lifecycle "^0.1.2" section-matter@^1.0.0: version "1.0.0" From 23344c088050c25c0b20098cd88745d61020dbd7 Mon Sep 17 00:00:00 2001 From: Blaine Kasten Date: Thu, 21 May 2020 10:14:20 -0500 Subject: [PATCH 02/14] Finish migration and keep previous compatibility --- .../gatsby-react-router-scroll/package.json | 4 +- .../gatsby-react-router-scroll/src/index.js | 2 - .../gatsby-react-router-scroll/src/index.ts | 5 ++ .../src/scroll-container.tsx | 64 +++++++++++++++++++ ...lBehaviorContext.js => scroll-handler.tsx} | 63 ++++++++++++------ .../{StateStorage.js => session-storage.ts} | 13 ++-- .../src/use-scroll-restoration.ts | 21 ++++++ 7 files changed, 142 insertions(+), 30 deletions(-) delete mode 100644 packages/gatsby-react-router-scroll/src/index.js create mode 100644 packages/gatsby-react-router-scroll/src/index.ts create mode 100644 packages/gatsby-react-router-scroll/src/scroll-container.tsx rename packages/gatsby-react-router-scroll/src/{ScrollBehaviorContext.js => scroll-handler.tsx} (50%) rename packages/gatsby-react-router-scroll/src/{StateStorage.js => session-storage.ts} (85%) create mode 100644 packages/gatsby-react-router-scroll/src/use-scroll-restoration.ts diff --git a/packages/gatsby-react-router-scroll/package.json b/packages/gatsby-react-router-scroll/package.json index 066bcc16503c7..b1ceedfee9026 100644 --- a/packages/gatsby-react-router-scroll/package.json +++ b/packages/gatsby-react-router-scroll/package.json @@ -33,9 +33,9 @@ "directory": "packages/gatsby-react-router-scroll" }, "scripts": { - "build": "babel src --out-dir . --ignore \"**/__tests__\"", + "build": "babel src --out-dir . --ignore \"**/__tests__\" --extensions \".ts,.tsx\"", "prepare": "cross-env NODE_ENV=production npm run build", - "watch": "babel -w src --out-dir . --ignore \"**/__tests__\"" + "watch": "babel -w src --out-dir . --ignore \"**/__tests__\" --extensions \".ts,.tsx\"" }, "engines": { "node": ">=10.13.0" diff --git a/packages/gatsby-react-router-scroll/src/index.js b/packages/gatsby-react-router-scroll/src/index.js deleted file mode 100644 index 66d9077fb7495..0000000000000 --- a/packages/gatsby-react-router-scroll/src/index.js +++ /dev/null @@ -1,2 +0,0 @@ -import ScrollBehaviorContext from "./ScrollBehaviorContext" -exports.ScrollContext = ScrollBehaviorContext diff --git a/packages/gatsby-react-router-scroll/src/index.ts b/packages/gatsby-react-router-scroll/src/index.ts new file mode 100644 index 0000000000000..4d5801e4d234f --- /dev/null +++ b/packages/gatsby-react-router-scroll/src/index.ts @@ -0,0 +1,5 @@ +import { ScrollHandler as ScrollContext } from "./scroll-handler" +import { ScrollContainer } from "./scroll-container" +import { useScrollRestoration } from "./use-scroll-restoration" + +export { ScrollContext, ScrollContainer, useScrollRestoration } diff --git a/packages/gatsby-react-router-scroll/src/scroll-container.tsx b/packages/gatsby-react-router-scroll/src/scroll-container.tsx new file mode 100644 index 0000000000000..aafc994061b23 --- /dev/null +++ b/packages/gatsby-react-router-scroll/src/scroll-container.tsx @@ -0,0 +1,64 @@ +import React from "react" +import ReactDOM from "react-dom" +import PropTypes from "prop-types" +import { ScrollContext } from "./scroll-handler" +import { SessionStorage } from "./session-storage" +import { Location } from "@reach/router" +import { Location as HLocation } from "history" + +const propTypes = { + scrollKey: PropTypes.string.isRequired, + shouldUpdateScroll: PropTypes.func, + children: PropTypes.element.isRequired, +} + +type Props = { + scrollKey: string + shouldUpdateScroll?: Function + children: React.ReactNode +} + +type PropsWithContextAndLocation = Props & { + context: SessionStorage + location: HLocation +} + +class ScrollContainerImplementation extends React.Component< + PropsWithContextAndLocation +> { + componentDidMount() { + const node = ReactDOM.findDOMNode(this) as Element + const { location, scrollKey } = this.props + + if (!node) return + + node.addEventListener("scroll", () => { + this.props.context.save(location, scrollKey, node.scrollTop) + }) + + const position = this.props.context.read(location, scrollKey) + node.scrollTo(0, position) + } + + render() { + return this.props.children + } +} + +export const ScrollContainer = (props: Props) => ( + + {({ location }) => ( + + {context => ( + + )} + + )} + +) + +ScrollContainer.propTypes = propTypes diff --git a/packages/gatsby-react-router-scroll/src/ScrollBehaviorContext.js b/packages/gatsby-react-router-scroll/src/scroll-handler.tsx similarity index 50% rename from packages/gatsby-react-router-scroll/src/ScrollBehaviorContext.js rename to packages/gatsby-react-router-scroll/src/scroll-handler.tsx index a8f9ae0807ede..62fe88b3d62b2 100644 --- a/packages/gatsby-react-router-scroll/src/ScrollBehaviorContext.js +++ b/packages/gatsby-react-router-scroll/src/scroll-handler.tsx @@ -1,14 +1,30 @@ import React from "react" +import { LocationContext } from "@reach/router" import PropTypes from "prop-types" -import SessionStorage from "./StateStorage" - -export default class ScrollContext extends React.Component { - constructor(props, context) { - super(props, context) - - this._stateStorage = new SessionStorage() +import { SessionStorage } from "./session-storage" + +export const ScrollContext = React.createContext( + new SessionStorage() +) +ScrollContext.displayName = `GatsbyScrollContext` + +type ShouldUpdateScrollFn = ( + prevRouterProps: LocationContext | undefined, + routerProps: LocationContext +) => boolean +type ShouldUpdateScroll = undefined | ShouldUpdateScrollFn + +export class ScrollHandler extends React.Component< + LocationContext & { shouldUpdateScroll: ShouldUpdateScroll } +> { + static propTypes = { + shouldUpdateScroll: PropTypes.func, + children: PropTypes.element.isRequired, + location: PropTypes.object.isRequired, } + _stateStorage: SessionStorage = new SessionStorage() + scrollListener = () => { const { key } = this.props.location @@ -23,9 +39,9 @@ export default class ScrollContext extends React.Component { this.props.location.key ) if (scrollPosition) { - this.windowScroll(scrollPosition) + this.windowScroll(scrollPosition, undefined) } else if (this.props.location.hash) { - this.scrollToHash(decodeURI(this.props.location.hash)) + this.scrollToHash(decodeURI(this.props.location.hash), undefined) } } @@ -33,7 +49,7 @@ export default class ScrollContext extends React.Component { window.removeEventListener(`scroll`, this.scrollListener) } - componentDidUpdate(prevProps) { + componentDidUpdate(prevProps: LocationContext): void { const { hash } = this.props.location const scrollPosition = this._stateStorage.read( @@ -47,13 +63,19 @@ export default class ScrollContext extends React.Component { } } - windowScroll = (position, prevProps) => { + windowScroll = ( + position: number, + prevProps: LocationContext | undefined + ): void => { if (this.shouldUpdateScroll(prevProps, this.props)) { window.scroll(0, position) } } - scrollToHash = (hash, prevProps) => { + scrollToHash = ( + hash: string, + prevProps: LocationContext | undefined + ): void => { const node = document.querySelector(hash) if (node && this.shouldUpdateScroll(prevProps, this.props)) { @@ -61,7 +83,10 @@ export default class ScrollContext extends React.Component { } } - shouldUpdateScroll = (prevRouterProps, routerProps) => { + shouldUpdateScroll = ( + prevRouterProps: LocationContext | undefined, + routerProps: LocationContext + ): boolean => { const { shouldUpdateScroll } = this.props if (!shouldUpdateScroll) { return true @@ -72,12 +97,10 @@ export default class ScrollContext extends React.Component { } render() { - return this.props.children + return ( + + {this.props.children} + + ) } } - -ScrollContext.propTypes = { - shouldUpdateScroll: PropTypes.func, - children: PropTypes.element.isRequired, - location: PropTypes.object.isRequired, -} diff --git a/packages/gatsby-react-router-scroll/src/StateStorage.js b/packages/gatsby-react-router-scroll/src/session-storage.ts similarity index 85% rename from packages/gatsby-react-router-scroll/src/StateStorage.js rename to packages/gatsby-react-router-scroll/src/session-storage.ts index 79460c8e908d4..d03675e0d520c 100644 --- a/packages/gatsby-react-router-scroll/src/StateStorage.js +++ b/packages/gatsby-react-router-scroll/src/session-storage.ts @@ -1,13 +1,14 @@ +import { Location } from "history" const STATE_KEY_PREFIX = `@@scroll|` const GATSBY_ROUTER_SCROLL_STATE = `___GATSBY_REACT_ROUTER_SCROLL` -export default class SessionStorage { - read(location, key) { +export class SessionStorage { + read(location: Location, key: string): number { const stateKey = this.getStateKey(location, key) try { const value = window.sessionStorage.getItem(stateKey) - return JSON.parse(value) + return value ? JSON.parse(value) : 0 } catch (e) { if (process.env.NODE_ENV !== `production`) { console.warn( @@ -23,11 +24,11 @@ export default class SessionStorage { return window[GATSBY_ROUTER_SCROLL_STATE][stateKey] } - return {} + return 0 } } - save(location, key, value) { + save(location: Location, key: string, value: number) { const stateKey = this.getStateKey(location, key) const storedValue = JSON.stringify(value) @@ -49,7 +50,7 @@ export default class SessionStorage { } } - getStateKey(location, key) { + getStateKey(location: Location, key: string) { const locationKey = location.key || location.pathname const stateKeyBase = `${STATE_KEY_PREFIX}${locationKey}` return key === null || typeof key === `undefined` diff --git a/packages/gatsby-react-router-scroll/src/use-scroll-restoration.ts b/packages/gatsby-react-router-scroll/src/use-scroll-restoration.ts new file mode 100644 index 0000000000000..8b3430eb02803 --- /dev/null +++ b/packages/gatsby-react-router-scroll/src/use-scroll-restoration.ts @@ -0,0 +1,21 @@ +import { ScrollContext } from "./scroll-handler" +import { useRef, useContext, useLayoutEffect } from "react" +import { useLocation } from "@reach/router" + +export function useScrollRestoration(identifier: string) { + const location = useLocation() + const state = useContext(ScrollContext) + const ref = useRef() + + useLayoutEffect(() => { + const position = state.read(location, identifier) + ref.current!.scrollTo(0, position) + }, []) + + return { + ref, + onScroll() { + state.save(location, identifier, ref.current!.scrollTop) + }, + } +} From 8c3cbe526cde5d78d53e297bc09beaf8b7c8789b Mon Sep 17 00:00:00 2001 From: Blaine Kasten Date: Thu, 21 May 2020 10:43:48 -0500 Subject: [PATCH 03/14] fix lints --- .../src/scroll-container.tsx | 19 +++++----- .../src/scroll-handler.tsx | 37 +++++++++++-------- .../src/session-storage.ts | 4 +- .../src/use-scroll-restoration.ts | 23 +++++++++--- 4 files changed, 50 insertions(+), 33 deletions(-) diff --git a/packages/gatsby-react-router-scroll/src/scroll-container.tsx b/packages/gatsby-react-router-scroll/src/scroll-container.tsx index aafc994061b23..9220fc6d5914b 100644 --- a/packages/gatsby-react-router-scroll/src/scroll-container.tsx +++ b/packages/gatsby-react-router-scroll/src/scroll-container.tsx @@ -12,27 +12,28 @@ const propTypes = { children: PropTypes.element.isRequired, } -type Props = { +interface IProps { scrollKey: string shouldUpdateScroll?: Function children: React.ReactNode } -type PropsWithContextAndLocation = Props & { +interface IPropsWithContextAndLocation extends IProps { context: SessionStorage location: HLocation } class ScrollContainerImplementation extends React.Component< - PropsWithContextAndLocation + IPropsWithContextAndLocation > { - componentDidMount() { + componentDidMount(): void { + // eslint-disable-next-line react/no-find-dom-node const node = ReactDOM.findDOMNode(this) as Element const { location, scrollKey } = this.props if (!node) return - node.addEventListener("scroll", () => { + node.addEventListener(`scroll`, () => { this.props.context.save(location, scrollKey, node.scrollTop) }) @@ -40,16 +41,16 @@ class ScrollContainerImplementation extends React.Component< node.scrollTo(0, position) } - render() { + render(): React.ReactNode { return this.props.children } } -export const ScrollContainer = (props: Props) => ( +export const ScrollContainer = (props: IProps): React.ReactNode => ( - {({ location }) => ( + {({ location }): React.ReactNode => ( - {context => ( + {(context): React.ReactNode => ( { + scrollListener = (): void => { const { key } = this.props.location - this._stateStorage.save(this.props.location, key, window.scrollY) + if (key) { + this._stateStorage.save(this.props.location, key, window.scrollY) + } } - componentDidMount() { + componentDidMount(): void { window.addEventListener(`scroll`, this.scrollListener) + let scrollPosition + const { key, hash } = this.props.location + + if (key) { + scrollPosition = this._stateStorage.read(this.props.location, key) + } - const scrollPosition = this._stateStorage.read( - this.props.location, - this.props.location.key - ) if (scrollPosition) { this.windowScroll(scrollPosition, undefined) - } else if (this.props.location.hash) { - this.scrollToHash(decodeURI(this.props.location.hash), undefined) + } else if (hash) { + this.scrollToHash(decodeURI(hash), undefined) } } - componentWillUnmount() { + componentWillUnmount(): void { window.removeEventListener(`scroll`, this.scrollListener) } componentDidUpdate(prevProps: LocationContext): void { - const { hash } = this.props.location + const { hash, key } = this.props.location + let scrollPosition + + if (key) { + scrollPosition = this._stateStorage.read(this.props.location, key) + } - const scrollPosition = this._stateStorage.read( - this.props.location, - this.props.location.key - ) if (scrollPosition) { this.windowScroll(scrollPosition, prevProps) } else if (hash) { @@ -96,7 +101,7 @@ export class ScrollHandler extends React.Component< return shouldUpdateScroll.call(this, prevRouterProps, routerProps) } - render() { + render(): React.ReactNode { return ( {this.props.children} diff --git a/packages/gatsby-react-router-scroll/src/session-storage.ts b/packages/gatsby-react-router-scroll/src/session-storage.ts index d03675e0d520c..74cbcdffc53bf 100644 --- a/packages/gatsby-react-router-scroll/src/session-storage.ts +++ b/packages/gatsby-react-router-scroll/src/session-storage.ts @@ -28,7 +28,7 @@ export class SessionStorage { } } - save(location: Location, key: string, value: number) { + save(location: Location, key: string, value: number): void { const stateKey = this.getStateKey(location, key) const storedValue = JSON.stringify(value) @@ -50,7 +50,7 @@ export class SessionStorage { } } - getStateKey(location: Location, key: string) { + getStateKey(location: Location, key: string): string { const locationKey = location.key || location.pathname const stateKeyBase = `${STATE_KEY_PREFIX}${locationKey}` return key === null || typeof key === `undefined` diff --git a/packages/gatsby-react-router-scroll/src/use-scroll-restoration.ts b/packages/gatsby-react-router-scroll/src/use-scroll-restoration.ts index 8b3430eb02803..1303ccb7937d0 100644 --- a/packages/gatsby-react-router-scroll/src/use-scroll-restoration.ts +++ b/packages/gatsby-react-router-scroll/src/use-scroll-restoration.ts @@ -2,20 +2,31 @@ import { ScrollContext } from "./scroll-handler" import { useRef, useContext, useLayoutEffect } from "react" import { useLocation } from "@reach/router" -export function useScrollRestoration(identifier: string) { +interface IScrollRestorationProps { + ref: React.MutableRefObject + onScroll(): void +} + +export function useScrollRestoration( + identifier: string +): IScrollRestorationProps { const location = useLocation() const state = useContext(ScrollContext) const ref = useRef() - useLayoutEffect(() => { - const position = state.read(location, identifier) - ref.current!.scrollTo(0, position) + useLayoutEffect((): void => { + if (ref.current) { + const position = state.read(location, identifier) + ref.current.scrollTo(0, position) + } }, []) return { ref, - onScroll() { - state.save(location, identifier, ref.current!.scrollTop) + onScroll(): void { + if (ref.current) { + state.save(location, identifier, ref.current.scrollTop) + } }, } } From e00e94a035186533c8f16cfaeb53244b160ee6ae Mon Sep 17 00:00:00 2001 From: Blaine Kasten Date: Fri, 5 Jun 2020 08:58:30 -0500 Subject: [PATCH 04/14] Update packages/gatsby-react-router-scroll/src/index.ts Co-authored-by: Ward Peeters --- packages/gatsby-react-router-scroll/src/index.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/gatsby-react-router-scroll/src/index.ts b/packages/gatsby-react-router-scroll/src/index.ts index 4d5801e4d234f..fae4680adeb26 100644 --- a/packages/gatsby-react-router-scroll/src/index.ts +++ b/packages/gatsby-react-router-scroll/src/index.ts @@ -1,5 +1,3 @@ -import { ScrollHandler as ScrollContext } from "./scroll-handler" -import { ScrollContainer } from "./scroll-container" -import { useScrollRestoration } from "./use-scroll-restoration" - -export { ScrollContext, ScrollContainer, useScrollRestoration } +export { ScrollHandler as ScrollContext } from "./scroll-handler" +export { ScrollContainer } from "./scroll-container" +export { useScrollRestoration } from "./use-scroll-restoration" From 722d9ab63a5bf5db0d74425bd05c42ba65cb1638 Mon Sep 17 00:00:00 2001 From: Blaine Kasten Date: Fri, 5 Jun 2020 08:59:01 -0500 Subject: [PATCH 05/14] Update packages/gatsby-react-router-scroll/src/scroll-container.tsx Co-authored-by: Ward Peeters --- packages/gatsby-react-router-scroll/src/scroll-container.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/gatsby-react-router-scroll/src/scroll-container.tsx b/packages/gatsby-react-router-scroll/src/scroll-container.tsx index 9220fc6d5914b..15d76255c7b3f 100644 --- a/packages/gatsby-react-router-scroll/src/scroll-container.tsx +++ b/packages/gatsby-react-router-scroll/src/scroll-container.tsx @@ -1,4 +1,4 @@ -import React from "react" +import * as React from "react" import ReactDOM from "react-dom" import PropTypes from "prop-types" import { ScrollContext } from "./scroll-handler" From 9ed5960cec5b83ee9507fe6ce3df8d30d1c5969c Mon Sep 17 00:00:00 2001 From: Blaine Kasten Date: Fri, 5 Jun 2020 08:59:48 -0500 Subject: [PATCH 06/14] Update packages/gatsby-react-router-scroll/src/scroll-handler.tsx Co-authored-by: Ward Peeters --- packages/gatsby-react-router-scroll/src/scroll-handler.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/gatsby-react-router-scroll/src/scroll-handler.tsx b/packages/gatsby-react-router-scroll/src/scroll-handler.tsx index bff6cbaf4970f..e43526eebab40 100644 --- a/packages/gatsby-react-router-scroll/src/scroll-handler.tsx +++ b/packages/gatsby-react-router-scroll/src/scroll-handler.tsx @@ -1,4 +1,4 @@ -import React from "react" +import * as React from "react" import { LocationContext } from "@reach/router" import PropTypes from "prop-types" import { SessionStorage } from "./session-storage" From 5424060984b37f76d384b71e981f774b6cb6b753 Mon Sep 17 00:00:00 2001 From: Blaine Kasten Date: Fri, 5 Jun 2020 09:19:01 -0500 Subject: [PATCH 07/14] fix a bug with state popping when saved state is set to 0 --- packages/gatsby-react-router-scroll/src/scroll-container.tsx | 3 ++- packages/gatsby-react-router-scroll/src/scroll-handler.tsx | 4 ++-- packages/gatsby-react-router-scroll/src/session-storage.ts | 2 +- .../gatsby-react-router-scroll/src/use-scroll-restoration.ts | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/gatsby-react-router-scroll/src/scroll-container.tsx b/packages/gatsby-react-router-scroll/src/scroll-container.tsx index 15d76255c7b3f..2b602862d6449 100644 --- a/packages/gatsby-react-router-scroll/src/scroll-container.tsx +++ b/packages/gatsby-react-router-scroll/src/scroll-container.tsx @@ -38,7 +38,8 @@ class ScrollContainerImplementation extends React.Component< }) const position = this.props.context.read(location, scrollKey) - node.scrollTo(0, position) + + ;(node.scrollTo || node.scroll).call(node, 0, position || 0) } render(): React.ReactNode { diff --git a/packages/gatsby-react-router-scroll/src/scroll-handler.tsx b/packages/gatsby-react-router-scroll/src/scroll-handler.tsx index e43526eebab40..5aff0c1745599 100644 --- a/packages/gatsby-react-router-scroll/src/scroll-handler.tsx +++ b/packages/gatsby-react-router-scroll/src/scroll-handler.tsx @@ -61,7 +61,7 @@ export class ScrollHandler extends React.Component< scrollPosition = this._stateStorage.read(this.props.location, key) } - if (scrollPosition) { + if (scrollPosition !== null) { this.windowScroll(scrollPosition, prevProps) } else if (hash) { this.scrollToHash(decodeURI(hash), prevProps) @@ -73,7 +73,7 @@ export class ScrollHandler extends React.Component< prevProps: LocationContext | undefined ): void => { if (this.shouldUpdateScroll(prevProps, this.props)) { - window.scroll(0, position) + ;(window.scrollTo || window.scroll)(0, position) } } diff --git a/packages/gatsby-react-router-scroll/src/session-storage.ts b/packages/gatsby-react-router-scroll/src/session-storage.ts index 74cbcdffc53bf..66b7e419bd921 100644 --- a/packages/gatsby-react-router-scroll/src/session-storage.ts +++ b/packages/gatsby-react-router-scroll/src/session-storage.ts @@ -3,7 +3,7 @@ const STATE_KEY_PREFIX = `@@scroll|` const GATSBY_ROUTER_SCROLL_STATE = `___GATSBY_REACT_ROUTER_SCROLL` export class SessionStorage { - read(location: Location, key: string): number { + read(location: Location, key: string): number | null { const stateKey = this.getStateKey(location, key) try { diff --git a/packages/gatsby-react-router-scroll/src/use-scroll-restoration.ts b/packages/gatsby-react-router-scroll/src/use-scroll-restoration.ts index 1303ccb7937d0..c7e2fb67eb696 100644 --- a/packages/gatsby-react-router-scroll/src/use-scroll-restoration.ts +++ b/packages/gatsby-react-router-scroll/src/use-scroll-restoration.ts @@ -17,7 +17,7 @@ export function useScrollRestoration( useLayoutEffect((): void => { if (ref.current) { const position = state.read(location, identifier) - ref.current.scrollTo(0, position) + ref.current.scrollTo(0, position || 0) } }, []) From 15596d669c76d642e0f64a86949854a01af1a694 Mon Sep 17 00:00:00 2001 From: Blaine Kasten Date: Fri, 5 Jun 2020 09:24:54 -0500 Subject: [PATCH 08/14] use scrollTo --- packages/gatsby-react-router-scroll/src/scroll-container.tsx | 2 +- packages/gatsby-react-router-scroll/src/scroll-handler.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/gatsby-react-router-scroll/src/scroll-container.tsx b/packages/gatsby-react-router-scroll/src/scroll-container.tsx index 2b602862d6449..4971306bc9ebd 100644 --- a/packages/gatsby-react-router-scroll/src/scroll-container.tsx +++ b/packages/gatsby-react-router-scroll/src/scroll-container.tsx @@ -39,7 +39,7 @@ class ScrollContainerImplementation extends React.Component< const position = this.props.context.read(location, scrollKey) - ;(node.scrollTo || node.scroll).call(node, 0, position || 0) + node.scrollTo(0, position || 0) } render(): React.ReactNode { diff --git a/packages/gatsby-react-router-scroll/src/scroll-handler.tsx b/packages/gatsby-react-router-scroll/src/scroll-handler.tsx index 5aff0c1745599..eb28e22dddf8d 100644 --- a/packages/gatsby-react-router-scroll/src/scroll-handler.tsx +++ b/packages/gatsby-react-router-scroll/src/scroll-handler.tsx @@ -73,7 +73,7 @@ export class ScrollHandler extends React.Component< prevProps: LocationContext | undefined ): void => { if (this.shouldUpdateScroll(prevProps, this.props)) { - ;(window.scrollTo || window.scroll)(0, position) + window.scrollTo(0, position) } } From d11426021fe5603efc8cc84b1cae3caec35ca0df Mon Sep 17 00:00:00 2001 From: Blaine Kasten Date: Fri, 5 Jun 2020 09:27:48 -0500 Subject: [PATCH 09/14] deprecate scroll container --- .../src/scroll-container.tsx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/packages/gatsby-react-router-scroll/src/scroll-container.tsx b/packages/gatsby-react-router-scroll/src/scroll-container.tsx index 4971306bc9ebd..042000ce9d7ba 100644 --- a/packages/gatsby-react-router-scroll/src/scroll-container.tsx +++ b/packages/gatsby-react-router-scroll/src/scroll-container.tsx @@ -1,3 +1,5 @@ +// TODO: In Gatsby v3, this file should be removed. +// We are deprecating this in V2 in favor of useScrollRestoration import * as React from "react" import ReactDOM from "react-dom" import PropTypes from "prop-types" @@ -23,9 +25,25 @@ interface IPropsWithContextAndLocation extends IProps { location: HLocation } +let hasNotWarnedDeprecation = true + class ScrollContainerImplementation extends React.Component< IPropsWithContextAndLocation > { + constructor(props) { + super(props) + + if (hasNotWarnedDeprecation) { + hasNotWarnedDeprecation = false + console.warn( + `Deprecation Warning: Gatsby is deprecated in Gatsby v2 and will be removed in Gatsby v3.` + ) + console.warn( + `Deprecation Warning: Update to the React hook alternative useScrollRestoration` + ) + } + } + componentDidMount(): void { // eslint-disable-next-line react/no-find-dom-node const node = ReactDOM.findDOMNode(this) as Element From 5d2ce5b247c4619cad46a14d20620b8389fa297f Mon Sep 17 00:00:00 2001 From: Blaine Kasten Date: Fri, 5 Jun 2020 09:34:07 -0500 Subject: [PATCH 10/14] add deprecation warning --- .../src/scroll-container.tsx | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/packages/gatsby-react-router-scroll/src/scroll-container.tsx b/packages/gatsby-react-router-scroll/src/scroll-container.tsx index 042000ce9d7ba..11934a9fca6c7 100644 --- a/packages/gatsby-react-router-scroll/src/scroll-container.tsx +++ b/packages/gatsby-react-router-scroll/src/scroll-container.tsx @@ -30,16 +30,28 @@ let hasNotWarnedDeprecation = true class ScrollContainerImplementation extends React.Component< IPropsWithContextAndLocation > { - constructor(props) { + constructor(props: IPropsWithContextAndLocation) { super(props) if (hasNotWarnedDeprecation) { hasNotWarnedDeprecation = false - console.warn( - `Deprecation Warning: Gatsby is deprecated in Gatsby v2 and will be removed in Gatsby v3.` - ) - console.warn( - `Deprecation Warning: Update to the React hook alternative useScrollRestoration` + console.log( + `Deprecation Warning: + + Gatsby is deprecated in Gatsby v2 and will be removed in Gatsby v3. + Update to the React hook alternative useScrollRestoration, like this:. + + \`\`\` + import React from 'react'; + import { useScrollRestoration } from 'gatsby-react-router-scroll'; + + function Component() { + const scrollRestoration = useScrollRestoration('${this.props.scrollKey}'); + + return
    ; + } + \`\`\` + ` ) } } From b93db89094ae3c3bce2047cc12c09537dbe513fa Mon Sep 17 00:00:00 2001 From: Blaine Kasten Date: Fri, 5 Jun 2020 09:44:59 -0500 Subject: [PATCH 11/14] Update packages/gatsby-react-router-scroll/src/scroll-container.tsx Co-authored-by: Ward Peeters --- packages/gatsby-react-router-scroll/src/scroll-container.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/gatsby-react-router-scroll/src/scroll-container.tsx b/packages/gatsby-react-router-scroll/src/scroll-container.tsx index 11934a9fca6c7..cacd000e9af1e 100644 --- a/packages/gatsby-react-router-scroll/src/scroll-container.tsx +++ b/packages/gatsby-react-router-scroll/src/scroll-container.tsx @@ -33,7 +33,7 @@ class ScrollContainerImplementation extends React.Component< constructor(props: IPropsWithContextAndLocation) { super(props) - if (hasNotWarnedDeprecation) { + if (process.env.NODE_ENV !== 'production' && hasNotWarnedDeprecation) { hasNotWarnedDeprecation = false console.log( `Deprecation Warning: From e9958ed9ab828511f113bb2b223ecff201fe8851 Mon Sep 17 00:00:00 2001 From: Blaine Kasten Date: Thu, 18 Jun 2020 09:00:45 -0500 Subject: [PATCH 12/14] fix lint --- packages/gatsby-react-router-scroll/src/scroll-container.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/gatsby-react-router-scroll/src/scroll-container.tsx b/packages/gatsby-react-router-scroll/src/scroll-container.tsx index cacd000e9af1e..5c09e57e777e6 100644 --- a/packages/gatsby-react-router-scroll/src/scroll-container.tsx +++ b/packages/gatsby-react-router-scroll/src/scroll-container.tsx @@ -33,7 +33,7 @@ class ScrollContainerImplementation extends React.Component< constructor(props: IPropsWithContextAndLocation) { super(props) - if (process.env.NODE_ENV !== 'production' && hasNotWarnedDeprecation) { + if (process.env.NODE_ENV !== `production` && hasNotWarnedDeprecation) { hasNotWarnedDeprecation = false console.log( `Deprecation Warning: From aa4d48d1ed798fbef2de80b454de87ef0789067a Mon Sep 17 00:00:00 2001 From: Blaine Kasten Date: Thu, 18 Jun 2020 09:03:17 -0500 Subject: [PATCH 13/14] remove null usage as it wasnt really used --- packages/gatsby-react-router-scroll/src/scroll-handler.tsx | 2 +- packages/gatsby-react-router-scroll/src/session-storage.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/gatsby-react-router-scroll/src/scroll-handler.tsx b/packages/gatsby-react-router-scroll/src/scroll-handler.tsx index eb28e22dddf8d..ac634f1e1749f 100644 --- a/packages/gatsby-react-router-scroll/src/scroll-handler.tsx +++ b/packages/gatsby-react-router-scroll/src/scroll-handler.tsx @@ -61,7 +61,7 @@ export class ScrollHandler extends React.Component< scrollPosition = this._stateStorage.read(this.props.location, key) } - if (scrollPosition !== null) { + if (scrollPosition) { this.windowScroll(scrollPosition, prevProps) } else if (hash) { this.scrollToHash(decodeURI(hash), prevProps) diff --git a/packages/gatsby-react-router-scroll/src/session-storage.ts b/packages/gatsby-react-router-scroll/src/session-storage.ts index 66b7e419bd921..74cbcdffc53bf 100644 --- a/packages/gatsby-react-router-scroll/src/session-storage.ts +++ b/packages/gatsby-react-router-scroll/src/session-storage.ts @@ -3,7 +3,7 @@ const STATE_KEY_PREFIX = `@@scroll|` const GATSBY_ROUTER_SCROLL_STATE = `___GATSBY_REACT_ROUTER_SCROLL` export class SessionStorage { - read(location: Location, key: string): number | null { + read(location: Location, key: string): number { const stateKey = this.getStateKey(location, key) try { From 9cc5396aa4a0175d36eff0d57e9df6601ebe075b Mon Sep 17 00:00:00 2001 From: Blaine Kasten Date: Thu, 18 Jun 2020 11:24:27 -0500 Subject: [PATCH 14/14] Fix tests and add more --- .../cypress/integration/scroll-behavior.js | 19 +++++++++++++++++++ .../production-runtime/src/pages/index.js | 5 +++++ .../production-runtime/src/pages/long-page.js | 2 ++ .../src/scroll-handler.tsx | 6 +++--- 4 files changed, 29 insertions(+), 3 deletions(-) diff --git a/e2e-tests/production-runtime/cypress/integration/scroll-behavior.js b/e2e-tests/production-runtime/cypress/integration/scroll-behavior.js index 2c6a87c569113..e1f135df5169d 100644 --- a/e2e-tests/production-runtime/cypress/integration/scroll-behavior.js +++ b/e2e-tests/production-runtime/cypress/integration/scroll-behavior.js @@ -34,6 +34,25 @@ describe(`Scroll behaviour`, () => { cy.getTestElement(`index-link`).click().waitForRouteChange() }) + it(`should scroll to hashes - even with encoded characters`, () => { + cy.visit(`/`).waitForRouteChange() + cy.getTestElement(`long-page-id`).click().waitForRouteChange() + + // UI should auto scroll to the id with a matching hash + cy.window().then(win => { + let idScrollY = win.scrollY + expect(win.scrollY).not.to.eq(0, 0) + + cy.scrollTo(`bottom`) + cy.go(`back`).waitForRouteChange() + cy.go(`forward`).waitForRouteChange() + + cy.window().then(updatedWindow => { + expect(updatedWindow.scrollY).not.to.eq(idScrollY) + }) + }) + }) + it(`should keep track of location.key`, () => { cy.visit(`/`).waitForRouteChange() diff --git a/e2e-tests/production-runtime/src/pages/index.js b/e2e-tests/production-runtime/src/pages/index.js index 422f65a76a86b..ef187e8010896 100644 --- a/e2e-tests/production-runtime/src/pages/index.js +++ b/e2e-tests/production-runtime/src/pages/index.js @@ -26,6 +26,11 @@ const IndexPage = ({ pageContext }) => ( To long page +
  • + + To long page (at id) + +
  • Another page using Index template diff --git a/e2e-tests/production-runtime/src/pages/long-page.js b/e2e-tests/production-runtime/src/pages/long-page.js index eada4b812c664..13cf9b803a6b9 100644 --- a/e2e-tests/production-runtime/src/pages/long-page.js +++ b/e2e-tests/production-runtime/src/pages/long-page.js @@ -12,6 +12,8 @@ const LongPage = () => ( Go back to the homepage - middle of the page
    +

    Special Hash ID

    +
    Go back to the homepage - bottom of the page diff --git a/packages/gatsby-react-router-scroll/src/scroll-handler.tsx b/packages/gatsby-react-router-scroll/src/scroll-handler.tsx index ac634f1e1749f..bd262232f9f7b 100644 --- a/packages/gatsby-react-router-scroll/src/scroll-handler.tsx +++ b/packages/gatsby-react-router-scroll/src/scroll-handler.tsx @@ -61,10 +61,10 @@ export class ScrollHandler extends React.Component< scrollPosition = this._stateStorage.read(this.props.location, key) } - if (scrollPosition) { - this.windowScroll(scrollPosition, prevProps) - } else if (hash) { + if (hash && scrollPosition === 0) { this.scrollToHash(decodeURI(hash), prevProps) + } else { + this.windowScroll(scrollPosition, prevProps) } }