Skip to content

Instantly share code, notes, and snippets.

@Cornally
Last active August 18, 2020 13:40
Show Gist options
  • Save Cornally/c7fa0b75d50abd09136abef7a844be90 to your computer and use it in GitHub Desktop.
Save Cornally/c7fa0b75d50abd09136abef7a844be90 to your computer and use it in GitHub Desktop.
React + React Router 5.x component for handling scroll restoration during forward/back history traversal. Use alongside your <Router> instance. This component should be accompanied by a routing strategy that defines when scroll restoration should be sidestepped by way of setting the component's `active` prop to `false`.
import { useEffect } from 'react'
import PropTypes from 'prop-types'
import { useHistory } from "react-router-dom"
/**
* If the user navigates by way of anything but the back button, scroll to the top.
* Otherwise, retrieve the previous scroll position when back is pressed, if a value
* has been previously stored.
* @param {String} key Internal react-router key provided by history listener
* @param {String} action Event action that triggered a history change
*/
const getOffset = (key, action, storageKey) => {
if (action === 'PUSH') {
return { x: 0, y: 0 }
}
const keys = JSON.parse(sessionStorage.getItem(storageKey))
return keys[key] || { x: 0, y: 0 }
}
/**
* Store the current scroll offset
* @param {String} key Internal react-router key provided by history listener
* @param {Object} value x and y coordinates of current route history entry
*/
const setOffset = (key, value, storageKey) => {
let keys = JSON.parse(sessionStorage.getItem(storageKey))
keys = {...keys, ...{[key]: value }}
sessionStorage.setItem(storageKey, JSON.stringify(keys))
}
/**
* Include this component alongside your <Router> declaration.
* @param {Boolean} active Control whether or not scrolling should be affected for the current application state.
* @param {String} storageKey Application-unique storage key for persisting scroll offsets in session storage.
*/
const ScrollRestoration = ({ active, storageKey }) => {
const history = useHistory()
useEffect(() => {
history.listen(({ ...args }, action) => {
// Navigation action not triggered by way of 'forward' or 'back' behavior
if (action === 'PUSH') {
setOffset(args.key, { x: window.scrollX, y: window.scrollY }, storageKey)
}
// Only modify the scroll if active
if (active) {
const { x, y } = getOffset(args.key, action, storageKey)
window.scrollTo(x, y)
}
})
}, [active, history, storageKey])
return (null)
}
ScrollRestoration.propTypes = {
/** Enable scroll offset restoration for next route change */
active: PropTypes.bool,
/** Unique identifier for your site's scroll restoration object referenced by sessionStorage */
storageKey: PropTypes.string.isRequired
}
ScrollRestoration.defaultProps = {
active: true
}
export default ScrollRestoration
import React, { Suspense } from 'react'
import {
BrowserRouter as Router,
Switch,
Redirect,
Route
} from "react-router-dom"
import { ROUTES } from '~constants'
import Container from '~components/container/container.component'
import ScrollRestoration from '~components/scroll-restoration/scroll-restoration.component'
import ViewLoading from '~components/view-loading/view-loading.component'
const ViewDashboard = React.lazy(() => import('~components/view-dashboard/view-dashboard.component'))
const ViewAccount = React.lazy(() => import('~components/view-account/view-account.component'))
const App = () => {
return (
<Router>
<Container>
<MenuBar />
<Suspense fallback={<ViewLoading />}>
<Switch>
<Route
component={ViewDashboard}
path={ROUTES.DASHBOARD}
/>
<Route
component={ViewAccount}
path={ROUTES.ACCOUNT}
/>
<Route path="/">
<Redirect to={ROUTES.DASHBOARD} />
</Route>
</Switch>
</Suspense>
</Container>
<ScrollRestoration storageKey="idz-scroll" />
</Router>
)
}
export default App
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment