NeoZoom.lua
NeoZoom.lua

Reputation: 2921

How to scroll to top by default using React Router V5?

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

Answers (2)

NeoZoom.lua
NeoZoom.lua

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

Tolumide
Tolumide

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.

Further Explanation

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

Related Questions