Skip to content

Instantly share code, notes, and snippets.

@ryanflorence
Last active September 5, 2020 16:46
Show Gist options
  • Save ryanflorence/39a37a85254159fd7a5ca54027e175dc to your computer and use it in GitHub Desktop.
Save ryanflorence/39a37a85254159fd7a5ca54027e175dc to your computer and use it in GitHub Desktop.
import React from "react";
import { Location } from "@reach/router";
let scrollPositions = {};
class ManageScrollImpl extends React.Component {
componentDidMount() {
try {
// session storage will throw for a few reasons
// - user settings
// - in-cognito/private browsing
// - who knows...
let storage = JSON.parse(sessionStorage.getItem("scrollPositions"));
if (storage) {
scrollPositions = JSON.parse(storage) || {};
let { key } = this.props.location;
if (scrollPositions[key]) {
window.scrollTo(0, scrollPositions[key]);
}
}
} catch (e) {}
window.addEventListener("scroll", this.listener);
}
componentWillUnmount() {
window.removeEventListener("scroll", this.listener);
}
componentDidUpdate() {
const { key } = this.props.location;
if (!scrollPositions[key]) {
// never seen this location before
window.scrollTo(0, 0);
} else {
// seen it
window.scrollTo(0, scrollPositions[key]);
}
}
listener = () => {
scrollPositions[this.props.location.key] = window.scrollY;
try {
sessionStorage.setItem(
"scrollPositions",
JSON.stringify(scrollPositions)
);
} catch (e) {}
};
render() {
return null;
}
}
export default () => (
<Location>
{({ location }) => <ManageScrollImpl location={location} />}
</Location>
);
@asktree
Copy link

asktree commented Oct 28, 2019

Is there any performance loss from having the listener constantly write to sessionStorage? Shouldn't it be possible to only update storage when I'm changing routes?

@okvv
Copy link

okvv commented Dec 23, 2019

Why not refactor componentDidUpdate block to:

const vistedLocationPosition = scrollPositions[this.props.location.key]
window.scrollTo(0, vistedLocationPosition || 0)

@aroraenterprise
Copy link

aroraenterprise commented Apr 2, 2020

For anyone looking for a functional component version of managing scroll position. I just import this component below my router <Router>...routes</Router><ManageScroll />

import { Location, WindowLocation } from "@reach/router";
import React from "react";

let scrollPositions: { [key: string]: number } = {};

type Props = {
	location: WindowLocation;
}

const ManageScrollImpl = ({ location }: Props) => {

	React.useEffect(() => {
		if (location.href) {
			window.scrollTo(0, scrollPositions[location.href || 0]);
		}
		window.addEventListener('scroll', listener);
		return () => window.removeEventListener('scroll', listener);
	}, [location?.href])

	const listener = () => {
		if (location && location.href) {
			scrollPositions[location.href] = window.scrollY;
		}
	};

	return null;
}

export const ManageScroll = () => (
	<Location>
		{({ location }) => <ManageScrollImpl location={location} />}
	</Location>
);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment