I'm using React-Router with a custom ScrollToTop
component to reset the scroll position to the top of the page on navigation. However, I have a specific use case where I need the scroll position to stay the same when clicking on category item.
ScrollToTop:
export const ScrollToTop = () => {
const { pathname } = useLocation();
useEffect(() => window.scrollTo(0, 0), [pathname]);
return null;
};
Router:
<BrowserRouter>
<ScrollToTop />
<Routes>
<Route path="/" element={<Layout />}>
<Route path="/" element={<HomePage />}>
<Route path=":category" element={<ExclusiveOffers />} />
</Route>
<Route path=":category/all" element={<AllProductsPage />} />
<Route path=":category/:name" element={<ProductPage />} />
<Route path="/account" element={<AccountPage />} />
<Route path="/checkout" element={<CheckoutPage />} />
</Route>
<Route path="/auth" element={<AuthPage />} />
<Route path="/login" element={<LoginPage />} />
<Route path="/register" element={<RegisterPage />} />
</Routes>
</BrowserRouter>
Category item:
<Box
component={Link}
to={title === "Top Categories"
? `/${item.query}`
: `/${item.query}/all`
}
You can accomplish skipping resetting the scroll-to-top a few different ways:
Convert ScrollToTop
to a layout route to wrap only the routes you want to scroll-to-top:
import { Outlet } from 'react-router-dom';
export const ScrollToTopLayout = () => {
const { pathname } = useLocation();
useEffect(() => window.scrollTo(0, 0), [pathname]);
return <Outlet />;
};
Example Usage:
<BrowserRouter>
<Routes>
<Route element={<Layout />}>
<Route element={<ScrollToTopLayout />}>
<Route index element={<HomePage />} />
<Route path=":category/:name" element={<ProductPage />} />
<Route path="account" element={<AccountPage />} />
<Route path="checkout" element={<CheckoutPage />} />
</Route>
<Route path=":category" element={<ExclusiveOffers />} />
<Route path=":category/all" element={<AllProductsPage />} />
</Route>
<Route element={<ScrollToTopLayout />}>
<Route path="auth" element={<AuthPage />} />
<Route path="login" element={<LoginPage />} />
<Route path="register" element={<RegisterPage />} />
</Route>
</Routes>
</BrowserRouter>
Update ScrollToTop
to consume explicit route paths to skip via props:
import { useLocation, matchPath } from 'react-router-dom';
export const ScrollToTop = ({ excludePaths = []}) => {
const { pathname } = useLocation();
useEffect(() => {
const isExcluded = excludePaths.some(
pattern => matchPath(pattern, pathname)
);
if (!isExcluded) {
window.scrollTo(0, 0);
}
}, [excludePaths, pathname]);
return null;
};
Example Usage:
<BrowserRouter>
<ScrollToTop excludePaths={[":category/all", ":category"]} />
</BrowserRouter>
Note however that ":category"
would match other non-"category" route paths, like "/account"
and "/login"
, so may not be ideal for all scenarios.
Pass some link state to skip scrolling to top upon navigating:
export const ScrollToTop = () => {
const { pathname, state } = useLocation();
useEffect(() => {
if (!state?.preventScrollToTop) {
window.scrollTo(0, 0);
}
}, [pathname, state]);
return null;
};
Example Usage:
<Box
component={Link}
to={title === "Top Categories"
? `/${item.query}`
: `/${item.query}/all`
}
state={{ preventScrollToTop: true }}
>
....
</Box>
You can use, mix, and apply any of the above implementations to cover more scenarios.