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/package.json b/packages/gatsby-react-router-scroll/package.json
index 8179e4583575c..4e8c2eab079ed 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.10.2",
- "scroll-behavior": "^0.9.12",
- "warning": "^3.0.0"
+ "@babel/runtime": "^7.10.2"
},
"devDependencies": {
"@babel/cli": "^7.10.1",
@@ -35,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/ScrollBehaviorContext.js b/packages/gatsby-react-router-scroll/src/ScrollBehaviorContext.js
deleted file mode 100644
index 8cb69e53fbd0b..0000000000000
--- a/packages/gatsby-react-router-scroll/src/ScrollBehaviorContext.js
+++ /dev/null
@@ -1,89 +0,0 @@
-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 {
- constructor(props, context) {
- super(props, context)
-
- this.scrollBehavior = new ScrollBehavior({
- addTransitionHook: history.listen,
- stateStorage: new SessionStorage(),
- getCurrentLocation: () => this.props.location,
- shouldUpdateScroll: this.shouldUpdateScroll,
- })
- }
-
- componentDidUpdate(prevProps) {
- const { location } = this.props
- const prevLocation = prevProps.location
-
- if (location === prevLocation) {
- return
- }
-
- const prevRouterProps = {
- location: prevProps.location,
- }
-
- this.scrollBehavior.updateScroll(prevRouterProps, { history, location })
- }
-
- componentWillUnmount() {
- this.scrollBehavior.stop()
- }
-
- getRouterProps() {
- const { location } = this.props
- return { location, history }
- }
-
- shouldUpdateScroll = (prevRouterProps, routerProps) => {
- const { shouldUpdateScroll } = this.props
- if (!shouldUpdateScroll) {
- 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)
- }
-
- render() {
- return (
-
- {React.Children.only(this.props.children)}
-
- )
- }
-}
-
-ScrollContext.propTypes = propTypes
-
-export default ScrollContext
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
deleted file mode 100644
index d0f30139c3da6..0000000000000
--- a/packages/gatsby-react-router-scroll/src/index.js
+++ /dev/null
@@ -1,4 +0,0 @@
-import ScrollBehaviorContext from "./ScrollBehaviorContext"
-import ScrollContainer from "./ScrollContainer"
-exports.ScrollContainer = ScrollContainer
-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..fae4680adeb26
--- /dev/null
+++ b/packages/gatsby-react-router-scroll/src/index.ts
@@ -0,0 +1,3 @@
+export { ScrollHandler as ScrollContext } from "./scroll-handler"
+export { ScrollContainer } from "./scroll-container"
+export { useScrollRestoration } from "./use-scroll-restoration"
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..5c09e57e777e6
--- /dev/null
+++ b/packages/gatsby-react-router-scroll/src/scroll-container.tsx
@@ -0,0 +1,96 @@
+// 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"
+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,
+}
+
+interface IProps {
+ scrollKey: string
+ shouldUpdateScroll?: Function
+ children: React.ReactNode
+}
+
+interface IPropsWithContextAndLocation extends IProps {
+ context: SessionStorage
+ location: HLocation
+}
+
+let hasNotWarnedDeprecation = true
+
+class ScrollContainerImplementation extends React.Component<
+ IPropsWithContextAndLocation
+> {
+ constructor(props: IPropsWithContextAndLocation) {
+ super(props)
+
+ if (process.env.NODE_ENV !== `production` && hasNotWarnedDeprecation) {
+ hasNotWarnedDeprecation = false
+ 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