web-dev-qa-db-fra.com

React Router v4 - Gardez la position de défilement lorsque vous changez de composant

J'en ai deux <Route>s créés avec react-router.

  • / cards -> Liste des jeux de cartes
  • / cards/1 -> Détail du jeu de cartes # 1

Lorsque l'utilisateur clique sur "Retour à la liste", je souhaite faire défiler l'utilisateur là où il se trouvait dans la liste.

Comment puis-je faire ceci?

8
Sancho

Exemple de travail à codesandbox

React Router v4 ne fournit pas de support prêt à l'emploi pour la restauration de défilement et tel qu'il est actuellement, ils ne le seront pas non plus. Dans la section React Router V4 - Scroll Restoration de leurs documents, vous pouvez en savoir plus à ce sujet.

Donc, il appartient à chaque développeur d'écrire une logique pour prendre en charge cela, bien que nous ayons quelques outils pour faire fonctionner cela.

element.scrollIntoView ()

.scrollIntoView() peut être appelée sur un élément et comme vous pouvez le deviner, il le fait défiler dans la vue. Le support est assez bon, actuellement, 97% des navigateurs le supportent. Source: icanuse

Le composant <Link /> Peut transmettre l'état

Le composant Link de React Router a un accessoire to que vous pouvez fournir un objet au lieu d'une chaîne. Voici à qui cela ressemble.

<Link to={{ pathname: '/card', state: 9 }}>Card nine</Link>

Nous pouvons utiliser state pour transmettre des informations au composant qui sera rendu. Dans cet exemple, un numéro est attribué à l'état, ce qui suffira pour répondre à votre question, vous verrez plus tard, mais cela peut être n'importe quoi. La route /card Rendant <Card /> Aura désormais accès à l'état variable dans props.location.state et nous pouvons utilisez-le comme nous le souhaitons.

Étiquetage de chaque élément de liste

Lors du rendu des différentes cartes, nous ajoutons une classe unique à chacune. De cette façon, nous avons un identifiant que nous pouvons transmettre et savons que cet élément doit être défilé dans la vue lorsque nous revenons à l'aperçu de la liste des cartes.

Solution

  1. <Cards /> Affiche une liste, chaque élément avec une classe unique;
  2. Lorsqu'un élément est cliqué, Link /> Transmet l'identifiant unique à <Card />;
  3. <Card /> Affiche les détails de la carte et un bouton de retour avec l'identifiant unique;
  4. Lorsque le bouton est cliqué et que <Cards /> Est monté, .scrollIntoView() fait défiler jusqu'à l'élément sur lequel vous avez cliqué précédemment en utilisant les données de props.location.state.

Voici quelques extraits de code de différentes parties.

// Cards component displaying the list of available cards.
// Link's to prop is passed an object where state is set to the unique id.
class Cards extends React.Component {
  componentDidMount() {
    const item = document.querySelector(
      ".restore-" + this.props.location.state
    );
    if (item) {
      item.scrollIntoView();
    }
  }

  render() {
    const cardKeys = Object.keys(cardData);
    return (
      <ul className="scroll-list">
        {cardKeys.map(id => {
          return (
            <Link
              to={{ pathname: `/cards/${id}`, state: id }}
              className={`card-wrapper restore-${id}`}
            >
              {cardData[id].name}
            </Link>
          );
        })}
      </ul>
    );
  }
}

// Card compoment. Link compoment passes state back to cards compoment
const Card = props => {
  const { id } = props.match.params;
  return (
    <div className="card-details">
      <h2>{cardData[id].name}</h2>
      <img alt={cardData[id].name} src={cardData[id].image} />
      <p>
        {cardData[id].description}&nbsp;<a href={cardData[id].url}>More...</a>
      </p>
      <Link
        to={{
          pathname: "/cards",
          state: props.location.state
        }}
      >
        <button>Return to list</button>
      </Link>
    </div>
  );
};

// App router compoment.
function App() {
  return (
    <div className="App">
      <Router>
        <div>
          <Route exact path="/cards" component={Cards} />
          <Route path="/cards/:id" component={Card} />
        </div>
      </Router>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
12
Roy Scheffers

Une autre solution possible est de rendre votre /cards/:id route en tant que modal plein écran et conservez le /cards route montée derrière

1
Jon Wyatt

Pour l'implémentation complète en utilisant Redux, vous pouvez le voir sur CodeSandbox .

Je l'ai fait en utilisant l'API historique.

  1. Enregistrer la position de défilement après un changement d'itinéraire.

  2. Restaurez la position de défilement lorsque l'utilisateur clique sur le bouton de retour.

Enregistrez la position de défilement dans getSnapshotBeforeUpdate et restaurez-la dans componentDidUpdate.

  // Saving scroll position.
  getSnapshotBeforeUpdate(prevProps) {
    const {
      history: { action },
      location: { pathname }
    } = prevProps;

    if (action !== "POP") {
      scrollData = { ...scrollData, [pathname]: window.pageYOffset };
    }

    return null;
  }

  // Restore scroll position.
  componentDidUpdate() {
    const {
      history: { action },
      location: { pathname }
    } = this.props;

    if (action === "POP") {
      if (scrollData[pathname]) {
        setTimeout(() =>
          window.scrollTo({
            left: 0,
            top: scrollData[pathname],
            behavior: "smooth"
          })
        );
      } else {
        setTimeout(window.scrollTo({ left: 0, top: 0 }));
      }
    } else {
      setTimeout(window.scrollTo({ left: 0, top: 0 }));
    }
  }
0
Agus Syahputra