react-router desplácese hacia arriba en cada transición

112

Tengo un problema al navegar a otra página, su posición permanecerá como la página anterior. Por lo que no se desplazará hacia la parte superior automáticamente. También intenté usar window.scrollTo(0, 0)en el enrutador onChange. También utilicé scrollBehavior para solucionar este problema, pero no funcionó. ¿Alguna sugerencia sobre esto?

adrian hartanto
fuente
¿No podrías hacer la lógica componentDidMountdel componente de la nueva ruta?
Yuya
sólo tiene que añadir document.body.scrollTop = 0;en el componentDidMountdel componente que se está mudando
John Ruddell
@Kujira ya agregué scrollTo dentro de componentDidMount () pero no funcionó.
adrian hartanto
@JohnRuddell Eso tampoco funcionó.
adrian hartanto
Tengo que usar document.getElementById ('root'). ScrollTop = 0 para que funcione
Sr. 14 de

Respuestas:

119

La documentación de React Router v4 contiene ejemplos de código para la restauración de scroll. Aquí está su primer ejemplo de código, que sirve como una solución en todo el sitio para "desplazarse hasta la parte superior" cuando se navega a una página:

class ScrollToTop extends Component {
  componentDidUpdate(prevProps) {
    if (this.props.location !== prevProps.location) {
      window.scrollTo(0, 0)
    }
  }

  render() {
    return this.props.children
  }
}

export default withRouter(ScrollToTop)

Luego renderícelo en la parte superior de su aplicación, pero debajo del enrutador:

const App = () => (
  <Router>
    <ScrollToTop>
      <App/>
    </ScrollToTop>
  </Router>
)

// or just render it bare anywhere you want, but just one :)
<ScrollToTop/>

^ copiado directamente de la documentación

Obviamente, esto funciona para la mayoría de los casos, pero hay más información sobre cómo tratar las interfaces con pestañas y por qué no se ha implementado una solución genérica.

mtl
fuente
3
También debe restablecer el enfoque del teclado al mismo tiempo que se desplaza hacia arriba. Escribí algo para cuidarlo: github.com/oaf-project/oaf-react-router
danielnixon
3
este extracto de los documentos Because browsers are starting to handle the “default case” and apps have varying scrolling needs (like this website!), we don’t ship with default scroll management. This guide should help you implement whatever scrolling needs you have.me entristece, porque todas las opciones para las que continúan dando ejemplos de código podrían integrarse en el control a través de la configuración de propiedades, con algunas convenciones sobre el comportamiento predeterminado que luego se pueden cambiar. Esto se siente súper hacky e inacabado, en mi humilde opinión. Significa que solo los desarrolladores avanzados de Reacción pueden hacer el enrutamiento correctamente y no #pitOfSuccess
snowcode
2
oaf-react-router parece abordar importantes problemas de accesibilidad que todos work-aroundsaquí han ignorado. +100 para @danielnixon
snowcode
ScrollToTop no está definido. ¿Cómo importarlo en App.js? importar ScrollToTop desde './ScrollToTop' no funciona.
anhtv13
@ anhtv13, debe hacer eso como una nueva pregunta para que pueda incluir el código al que hace referencia.
mtl
92

pero las clases son tan 2018

Implementación de ScrollToTop con React Hooks

ScrollToTop.js

import { useEffect } from 'react';
import { withRouter } from 'react-router-dom';

function ScrollToTop({ history }) {
  useEffect(() => {
    const unlisten = history.listen(() => {
      window.scrollTo(0, 0);
    });
    return () => {
      unlisten();
    }
  }, []);

  return (null);
}

export default withRouter(ScrollToTop);

Uso:

<Router>
  <Fragment>
    <ScrollToTop />
    <Switch>
        <Route path="/" exact component={Home} />
    </Switch>
  </Fragment>
</Router>

ScrollToTop también se puede implementar como un componente contenedor:

ScrollToTop.js

import React, { useEffect, Fragment } from 'react';
import { withRouter } from 'react-router-dom';

function ScrollToTop({ history, children }) {
  useEffect(() => {
    const unlisten = history.listen(() => {
      window.scrollTo(0, 0);
    });
    return () => {
      unlisten();
    }
  }, []);

  return <Fragment>{children}</Fragment>;
}

export default withRouter(ScrollToTop);

Uso:

<Router>
  <ScrollToTop>
    <Switch>
        <Route path="/" exact component={Home} />
    </Switch>
  </ScrollToTop>
</Router>
zurfyx
fuente
2
La mejor solución que he visto hasta ahora en Internet. Solo estoy un poco confundido por qué el proyecto react-router no agrega una propiedad scrollToTopa <Route>. Parece ser una característica de uso muy frecuente.
stanleyxu2005
Buen trabajo amigo. Ayudó mucho.
VaibS
¿Cuál sería una forma adecuada de realizar una prueba unitaria de esto?
Matt
deberías comprobar action !== 'POP'. El oyente acepta la acción como
segundo argumento
Además, puede usar useHistory hook en lugar de withRouter HOC
Atombit hace
29

Esta respuesta es para código heredado, para enrutador v4 + verifique otras respuestas

<Router onUpdate={() => window.scrollTo(0, 0)} history={createBrowserHistory()}>
  ...
</Router>

Si no funciona, debe encontrar la razón. Tambien adentrocomponentDidMount

document.body.scrollTop = 0;
// or
window.scrollTo(0,0);

podrías usar:

componentDidUpdate() {
  window.scrollTo(0,0);
}

podría agregar alguna bandera como "scrolled = false" y luego en la actualización:

componentDidUpdate() {
  if(this.scrolled === false){
    window.scrollTo(0,0);
    scrolled = true;
  }
}
Lukas Liesis
fuente
Actualicé la respuesta sin getDomNode porque, de hecho, nunca tuve que usarla para desplazarme hacia la parte superior. Acabo de usarwindow.scrollTo(0,0);
Lukas Liesis
3
onUpdatehook está obsoleto en react-router v4 - solo quiero señalar eso
Dragos Rizescu
@DragosRizescu ¿Alguna guía sobre el uso de este método sin el onUpdategancho?
Matt Voda
1
@MattVoda Puede escuchar los cambios en el historial, ver ejemplos: github.com/ReactTraining/history
Lukas Liesis
3
@MattVoda escribió una respuesta a continuación sobre cómo puede lograr eso con react-router-v4
Dragos Rizescu
28

Para react-router v4 , aquí hay una aplicación create-react-app que logra la restauración del desplazamiento: http://router-scroll-top.surge.sh/ .

Para lograr esto, puede crear decorar el Routecomponente y aprovechar los métodos del ciclo de vida:

import React, { Component } from 'react';
import { Route, withRouter } from 'react-router-dom';

class ScrollToTopRoute extends Component {
  componentDidUpdate(prevProps) {
    if (this.props.path === this.props.location.pathname && this.props.location.pathname !== prevProps.location.pathname) {
      window.scrollTo(0, 0)
    }
  }

  render() {
    const { component: Component, ...rest } = this.props;

    return <Route {...rest} render={props => (<Component {...props} />)} />;
  }
}

export default withRouter(ScrollToTopRoute);

En el componentDidUpdatepodemos verificar cuándo cambia el nombre de ruta de ubicación y hacer coincidirlo con elpath propiedad y, si están satisfechos, restaurar el desplazamiento de la ventana.

Lo bueno de este enfoque es que podemos tener rutas que restauran el desplazamiento y rutas que no restauran el desplazamiento.

A continuación, se App.jsmuestra un ejemplo de cómo puede utilizar lo anterior:

import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Link } from 'react-router-dom';
import Lorem from 'react-lorem-component';
import ScrollToTopRoute from './ScrollToTopRoute';
import './App.css';

const Home = () => (
  <div className="App-page">
    <h2>Home</h2>
    <Lorem count={12} seed={12} />
  </div>
);

const About = () => (
  <div className="App-page">
    <h2>About</h2>
    <Lorem count={30} seed={4} />
  </div>
);

const AnotherPage = () => (
  <div className="App-page">
    <h2>This is just Another Page</h2>
    <Lorem count={12} seed={45} />
  </div>
);

class App extends Component {
  render() {
    return (
      <Router>
        <div className="App">
          <div className="App-header">
            <ul className="App-nav">
              <li><Link to="/">Home</Link></li>
              <li><Link to="/about">About</Link></li>
              <li><Link to="/another-page">Another Page</Link></li>
            </ul>
          </div>
          <Route exact path="/" component={Home} />
          <ScrollToTopRoute path="/about" component={About} />
          <ScrollToTopRoute path="/another-page" component={AnotherPage} />
        </div>
      </Router>
    );
  }
}

export default App;

Del código anterior, lo que es interesante señalar es que solo al navegar hacia /abouto se realizará /another-pagela acción de desplazamiento hacia arriba. Sin embargo, al continuar/ no se producirá ninguna restauración de desplazamiento.

El código base completo se puede encontrar aquí: https://github.com/rizedr/react-router-scroll-top

Dragos Rizescu
fuente
1
¡Justo lo que estaba buscando! Tuve que agregar scrollTo en el componentDidMount de ScrollToTopRoute, ya que tengo mis rutas envueltas en un componente Switch (también eliminé el if, ya que quería que se disparara en cada montaje)
tocallaghan
En el render de su ScrollToTopRoute, no necesita especificar render = {props => (<Component {... props} />)}. Simplemente usar {... rest} en la ruta funcionará.
Finickyflame
Esta respuesta me proporcionó todas las herramientas para lograr una solución y corregir mi error. Muchas gracias.
Nulo es verdadero
No está funcionando completamente. Si sigue el enlace del proyecto, se abrirá el archivo Home. Ahora desplácese hasta la parte inferior del Homey ahora vaya a 'Otra página' y no se desplace. Ahora, si vuelve a Home, esta página se desplazará hasta la parte superior. Pero de acuerdo con su respuesta, la homepágina debe mantenerse desplazada.
aditya81070
18

Es de notar que el onUpdate={() => window.scrollTo(0, 0)} método está desactualizado.

Aquí hay una solución simple para react-router 4+.

const history = createBrowserHistory()

history.listen(_ => {
    window.scrollTo(0, 0)  
})

<Router history={history}>
frijol azul
fuente
Esta debería ser la respuesta correcta si se usa el historial. Cubre todas las eventualidades, incluido hacer clic en un enlace a la página en la que se encuentra actualmente. El único problema que encontré es que scrollTo no se disparó y necesitaba estar envuelto en un setTimeout (window.scrollTo (0, 0), 0); Todavía no entiendo por qué, pero oye ho
sidonaldson
2
Tendrá problemas cuando agregue #y ?al final de nuestra URL. En el primer caso, debe desplazarse no hacia la parte superior, en el segundo caso, no debe desplazarse en absoluto
Arseniy-II
1
Desafortunadamente, esto no funciona con BrowserRouter.
VaibS
12

Si está ejecutando React 16.8+, esto es sencillo de manejar con un componente que desplazará la ventana hacia arriba en cada navegación:
Aquí está en el componente scrollToTop.js

import { useEffect } from "react";
import { useLocation } from "react-router-dom";

export default function ScrollToTop() {
  const { pathname } = useLocation();

  useEffect(() => {
    window.scrollTo(0, 0);
  }, [pathname]);

  return null;
}

Luego renderícelo en la parte superior de su aplicación, pero debajo de Router
Here está en app.js

import ScrollToTop from "./scrollToTop";

function App() {
  return (
    <Router>
      <ScrollToTop />
      <App />
    </Router>
  );
}

o en index.js

import ScrollToTop from "./scrollToTop";

ReactDOM.render(
    <BrowserRouter>
        <ScrollToTop />
        <App />
    </BrowserRouter>
    document.getElementById("root")
);
Saeed Rohani
fuente
7

Tuve el mismo problema con mi aplicación. El uso del siguiente fragmento de código me ayudó a desplazarme a la parte superior de la página al hacer clic en el siguiente botón.

<Router onUpdate={() => window.scrollTo(0, 0)} history= {browserHistory}>
...
</Router>

Sin embargo, el problema persistía en la parte posterior del navegador. Después de muchas pruebas, me di cuenta de que esto se debía al objeto del historial de la ventana del navegador, que tiene una propiedad scrollRestoration que se configuró en auto. Establecer esto en manual resolvió mi problema.

function scrollToTop() {
    window.scrollTo(0, 0)
    if ('scrollRestoration' in history) {
        history.scrollRestoration = 'manual';
    }
}

<Router onUpdate= {scrollToTop} history={browserHistory}>
....
</Router>
Chinoy de Jhalaa
fuente
6

En un componente a continuación <Router>

Simplemente agregue un React Hook (en caso de que no esté usando una clase React)

  React.useEffect(() => {
    window.scrollTo(0, 0);
  }, [props.location]);
Thomas Aumaitre
fuente
4

Quiero compartir mi solución para aquellos que están usando, react-router-dom v5ya que ninguna de estas soluciones v4 hizo el trabajo por mí.

Lo que resolvió mi problema fue instalar react-router-scroll-top y poner la envoltura de <App />esta manera:

const App = () => (
  <Router>
    <ScrollToTop>
      <App/>
    </ScrollToTop>
  </Router>
)

¡y eso es! ¡funcionó!

Luis Febro
fuente
4

Los hooks son componibles, y desde React Router v5.1 tenemos un useHistory()hook. Entonces, basado en la respuesta de @ zurfyx, he creado un gancho reutilizable para esta funcionalidad:

// useScrollTop.ts
import { useHistory } from 'react-router-dom';
import { useEffect } from 'react';

/*
 * Registers a history listener on mount which
 * scrolls to the top of the page on route change
 */
export const useScrollTop = () => {
    const history = useHistory();
    useEffect(() => {
        const unlisten = history.listen(() => {
            window.scrollTo(0, 0);
        });
        return unlisten;
    }, [history]);
};
Kris Dover
fuente
4

Un React Hook que puede agregar a su componente de ruta. Utilizando en useLayoutEffectlugar de oyentes personalizados.

import React, { useLayoutEffect } from 'react';
import { Switch, Route, useLocation } from 'react-router-dom';

export default function Routes() {
  const location = useLocation();
  // Scroll to top if path changes
  useLayoutEffect(() => {
    window.scrollTo(0, 0);
  }, [location.pathname]);

  return (
      <Switch>
        <Route exact path="/">

        </Route>
      </Switch>
  );
}

Actualización : actualizado para usar en useLayoutEffectlugar de useEffect, para un jank menos visual. Aproximadamente esto se traduce en:

  • useEffect: renderizar componentes -> pintar en pantalla -> desplazarse hacia arriba (ejecutar efecto)
  • useLayoutEffect: renderizar componentes -> desplazarse hacia arriba (ejecutar efecto) -> pintar en pantalla

Dependiendo de si está cargando datos (piense en hilanderos) o si tiene animaciones de transición de página, useEffectpuede funcionar mejor para usted.

Gabe M
fuente
3

Mi solución: un componente que estoy usando en mis componentes de pantallas (donde quiero un desplazamiento hacia arriba).

import { useLayoutEffect } from 'react';

const ScrollToTop = () => {
    useLayoutEffect(() => {
        window.scrollTo(0, 0);
    }, []);

    return null;
};

export default ScrollToTop;

Esto conserva la posición de desplazamiento al retroceder. Usar useEffect () tuvo errores para mí, cuando retrocedía, el documento se desplazaba hacia la parte superior y también tenía un efecto de parpadeo cuando se cambiaba la ruta en un documento ya desplazado.

Sergiu
fuente
3

Reaccionar ganchos 2020 :)

import React, { useLayoutEffect } from 'react';
import { useLocation } from 'react-router-dom';

const ScrollToTop: React.FC = () => {
  const { pathname } = useLocation();
  useLayoutEffect(() => {
    window.scrollTo(0, 0);
  }, [pathname]);

  return null;
};

export default ScrollToTop;
Kepro
fuente
¿Puedes explicar esta parte? const ScrollToTop: React.FC = () => {No entiendo lo que ScrollToTop: React.FCsignifica
beeftosino
1
definición mecanografiada
Kepro
2

Escribí un componente de orden superior llamado withScrollToTop. Este HOC tiene dos banderas:

  • onComponentWillMount- Ya sea para desplazarse hacia arriba en la navegación ( componentWillMount)
  • onComponentDidUpdate- Si desea desplazarse hacia arriba al actualizar ( componentDidUpdate). Esta bandera es necesaria en los casos en los que el componente no está desmontado pero se produce un evento de navegación, por ejemplo, de /users/1a /users/2.

// @flow
import type { Location } from 'react-router-dom';
import type { ComponentType } from 'react';

import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';

type Props = {
  location: Location,
};

type Options = {
  onComponentWillMount?: boolean,
  onComponentDidUpdate?: boolean,
};

const defaultOptions: Options = {
  onComponentWillMount: true,
  onComponentDidUpdate: true,
};

function scrollToTop() {
  window.scrollTo(0, 0);
}

const withScrollToTop = (WrappedComponent: ComponentType, options: Options = defaultOptions) => {
  return class withScrollToTopComponent extends Component<Props> {
    props: Props;

    componentWillMount() {
      if (options.onComponentWillMount) {
        scrollToTop();
      }
    }

    componentDidUpdate(prevProps: Props) {
      if (options.onComponentDidUpdate &&
        this.props.location.pathname !== prevProps.location.pathname) {
        scrollToTop();
      }
    }

    render() {
      return <WrappedComponent {...this.props} />;
    }
  };
};

export default (WrappedComponent: ComponentType, options?: Options) => {
  return withRouter(withScrollToTop(WrappedComponent, options));
};

Para usarlo:

import withScrollToTop from './withScrollToTop';

function MyComponent() { ... }

export default withScrollToTop(MyComponent);
Yangshun Tay
fuente
1

Aquí hay otro método.

Para react-router v4 , también puede vincular un oyente para cambiar el evento del historial, de la siguiente manera:

let firstMount = true;
const App = (props) => {
    if (typeof window != 'undefined') { //incase you have server-side rendering too             
        firstMount && props.history.listen((location, action) => {
            setImmediate(() => window.scrollTo(0, 0)); // ive explained why i used setImmediate below
        });
        firstMount = false;
    }

    return (
        <div>
            <MyHeader/>            
            <Switch>                            
                <Route path='/' exact={true} component={IndexPage} />
                <Route path='/other' component={OtherPage} />
                // ...
             </Switch>                        
            <MyFooter/>
        </div>
    );
}

//mounting app:
render((<BrowserRouter><Route component={App} /></BrowserRouter>), document.getElementById('root'));

El nivel de desplazamiento se establecerá en 0 sin setImmediate()también si la ruta se cambia haciendo clic en un enlace, pero si el usuario presiona el botón Atrás en el navegador, no funcionará ya que el navegador restablece el nivel de desplazamiento manualmente al nivel anterior cuando se presiona el botón Atrás. , por lo que al usar setImmediate()hacemos que nuestra función se ejecute después de que el navegador termine de restablecer el nivel de desplazamiento, nos dará el efecto deseado.

Zeus
fuente
1

con React router dom v4 puedes usar

cree un componente scrollToTopComponent como el siguiente

class ScrollToTop extends Component {
    componentDidUpdate(prevProps) {
      if (this.props.location !== prevProps.location) {
        window.scrollTo(0, 0)
      }
    }

    render() {
      return this.props.children
    }
}

export default withRouter(ScrollToTop)

o si está usando pestañas, use algo como el siguiente

class ScrollToTopOnMount extends Component {
    componentDidMount() {
      window.scrollTo(0, 0)
    }

    render() {
      return null
    }
}

class LongContent extends Component {
    render() {
      <div>
         <ScrollToTopOnMount/>
         <h1>Here is my long content page</h1>
      </div>
    }
}

// somewhere else
<Route path="/long-content" component={LongContent}/>

espero que esta ayuda para más información sobre la restauración de desplazamiento vist hay documentos liebre reaccionan restauración de desplazamiento dom enrutador

user2907964
fuente
0

Descubrí que ReactDOM.findDomNode(this).scrollIntoView()está funcionando. Lo puse dentro componentDidMount().

adrian hartanto
fuente
1
son cosas internas indocumentadas que podrían cambiar sin darse cuenta y su código dejaría de funcionar.
Lukas Liesis
0

Para aplicaciones más pequeñas, con 1-4 rutas, puede intentar piratearlo con redirección al elemento DOM superior con #id en lugar de solo una ruta. Entonces no hay necesidad de ajustar Rutas en ScrollToTop o usar métodos de ciclo de vida.

yakato
fuente
0

En su router.js, simplemente agregue esta función en el routerobjeto. Esto hará el trabajo.

scrollBehavior() {
        document.getElementById('app').scrollIntoView();
    },

Me gusta esto,

**Routes.js**

import vue from 'blah!'
import Router from 'blah!'

let router = new Router({
    mode: 'history',
    base: process.env.BASE_URL,
    scrollBehavior() {
        document.getElementById('app').scrollIntoView();
    },
    routes: [
            { url: "Solar System" },
            { url: "Milky Way" },
            { url: "Galaxy" },
    ]
});
Debu Shinobi
fuente
-1
render() {
    window.scrollTo(0, 0)
    ...
}

Puede ser una solución simple en caso de que no se cambien los accesorios y no se active componentDidUpdate ().

Ingvar Lond
fuente
-11

Esto es hacky (pero funciona): solo agrego

window.scrollTo (0,0);

renderizar ();

JJ
fuente
2
por lo que se desplazará hacia arriba cada vez que cambie el estado y se vuelva a procesar, no cuando cambie la navegación. Solo revisa mi respuesta
Lukas Liesis