Reputation: 2921
As title I want to scroll to the top every time I use <Redirect to="...">
for redirecting from one page to the other. None of the existing answers (especially this popular thread: react-router scroll to top on every transition) on this site work.
To be specific, I want React 16.8+, functional component, react router V5-approach. I have tried using the following
const {path} = useRouteMatch()
To implement the following component, as a wrapper to the entire <div className="App">
, so I don't have to wrap for each component that wants this effect:
ScrollToTop.js:
import {useEffect} from "react";
import {useRouteMatch} from "react-router-dom";
export const ScrollToTop = () => {
const {path} = useRouteMatch()
useEffect(() => {
console.log("path =", path)
window.scrollTo(0, 0)
}, [path])
return null
}
Part of what I'm working on:
import React, {useCallback, useEffect, useState} from "react";
import {ItemListPage} from "../../commons/ItemListPage/ItemListPage";
import {CreateButton} from "../../commons/buttons/CreateButton";
import CreateProblemModal from "../Modal/CreateProblemModal";
import {problemService} from "../../../services/services";
import {Spinner} from "../../commons/Spinner";
import {ProblemEditor} from "../ProblemEditor";
import {Link, Redirect, Route} from "react-router-dom";
import {TableCell} from "../../../utils/TableCell";
import {ProblemContext} from "./ProblemContext";
import {ScrollToTop} from "../../commons/scrolling/ScrollToTop";
export const useProblemList = () => {
const [problems, setProblems] = useState();
const addProblem = (problem) => {
problems.push(problem);
setProblems(problems);
};
return {problems, setProblems, addProblem}
};
const ProblemList = () => {
const [showCreateProblemModal, setShowCreateProblemModal] = useState(false)
const {problems, setProblems} = useProblemList();
const [currentProblem, setCurrentProblem] = useState();
const [shouldRedirect, setShouldRedirect] = useState(false)
const refetchProblem = useCallback((problemId) => {
problemService.getAllProblems()
.then(problems => {
setProblems(problems)
setCurrentProblem(problems.find(problem => parseInt(problem.id) === parseInt(problemId)))
})
}, [setProblems, setCurrentProblem])
useEffect(() => {
if (!problems || problems.length === 0) {
refetchProblem()
}
}, [problems, refetchProblem]);
const onProblemCreated = (problemId) => {
refetchProblem(problemId)
setShouldRedirect(true)
}
if (!problems || (shouldRedirect && !currentProblem)) {
return <Spinner/>
}
return (
<>
<ScrollToTop/>
{shouldRedirect?
<Redirect to={`problems/:problemId/edit`}/> : ""}
<Route path="/problems" exact>
<div className="problem-list font-poppins">
<div style={{paddingTop: "20px", paddingBottom: "150px"}}>
<div style={{display: "flex", justifyContent: "center"}}>
<ItemListPage title="Problem List"
width="1000px"
filterItems={["Filter", "Id", "tags"]}
Button={() =>
<CreateButton onClick={() => setShowCreateProblemModal(true)}/>}
tableHeaders={[
<TableCell>#</TableCell>,
<TableCell>Problem Title</TableCell>,
<TableCell>Tags</TableCell>
]}
tableRowGenerator={{
list: problems,
key: (problem) => problem.id,
data: (problem) => [
<TableCell>
<Link to={`/problems/${problem.id}/edit`}
onClick={() => setCurrentProblem(problem)}>
{problem.id}</Link>
</TableCell>,
<TableCell>
<Link to={`/problems/${problem.id}/edit`}
onClick={() => setCurrentProblem(problem)}>
{problem.title}</Link>
</TableCell>,
<TableCell>
<span className="tag is-link">Functions</span>
</TableCell>,
]
}}
tableDataStyle={{textAlign: "left"}}/>
<CreateProblemModal show={showCreateProblemModal}
onClose={() => setShowCreateProblemModal(false)}
onProblemCreated={onProblemCreated}/>
</div>
</div>
</div>
</Route>
<Route path="/problems/:problemId/edit">
<ProblemContext.Provider value={{
currentProblem, setCurrentProblem, refetchProblem, setShouldRedirect}}>
<ProblemEditor/>
</ProblemContext.Provider>
</Route>
</>
)
}
export {ProblemList};
Upvotes: 0
Views: 387
Reputation: 2921
The answer by @Tolumide is correct, AND if you still have problem, remove all occurrences of
overflow-y: auto;
In <ScrollToTop>
component, regarding why using
const {pathname} = useLocation()
instead of
const {path} = useRouteMatch()
Here are my conclusion by trying:
useLocation()::pathname
reports where the user is, regardless of where <ScrollToTop>
is.useRouteMatch()::path
reports where the component, <ScrollToTop>
, is, regardless of where the user is.Upvotes: 0
Reputation: 994
Consider changing your scrollToTop Implementation to this:
import * as React from "react";
import { useLocation } from "react-router";
export const ScrollToTop = () => {
const { pathname } = useLocation();
React.useEffect(() => {
window.scrollTo(0, 0);
}, [pathname]);
return null;
};
export const ScrollToTopOnMount = () => {
React.useEffect(() => {
window.scrollTo(0, 0);
});
return null;
};
Note the specific use of useLocation
as against useRouteMatch
You can read more on the React router docs.
To put a better explanation to this:
Let's consider a route /names/:id
where id is dynamic.
If you used:
const {path} = useRouteMatch();
path would only detect the undynamic part of this url, if you were to console.log(path)
, you would get something like:
/names/:id
However, if you used:
const {pathname} = useLocation();
you would get every changes including the dynamic id. So, if you were to console.log(pathname)
you would see:
/names/theDynamicId
where id="theDynamicId"
The documentation gives a clear description of useLocation
The useLocation hook returns the location object that represents the
current URL. You can think about it like a useState that returns a new
location whenever the URL changes.
Upvotes: 2