Last active
August 18, 2020 13:40
-
-
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`.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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