Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React Router v4 - Keep scrolling position when switching components

Tags:

I have two <Route>s created with react-router.

  • /cards -> List of cards game
  • /cards/1 -> Detail of card game #1

When the user clicks on the "Return to list", I want to scroll the user where he was on the list.

How can I do this?

like image 404
Sancho Avatar asked Aug 18 '18 08:08

Sancho


People also ask

How can I retain the scroll position of a scrollable area when pressing back button?

During page unload, get the scroll position and store it in local storage. Then during page load, check local storage and set that scroll position.

How do you not scroll on top when state changes in react?

2022 Hacky Solution: useEffect(() => { const el = document. getElementsByClassName('my-classname')?.[0]; const newTimeoutRef = setTimeout(() => el?. scrollTo(0, scrollTop), 50); return () => clearTimeout(newTimeoutRef); }, [scrollTop]);


1 Answers

Working example at codesandbox

React Router v4 does not provide out of the box support for scroll restoration and as it currently stands they won't either. In section React Router V4 - Scroll Restoration of their docs you can read more about it.

So, it's up to every developer to write logic to support this although, we do have some tools to make this work.

element.scrollIntoView()

.scrollIntoView() can be called on an element and as you can guess, it scrolls it into view. Support is quite good, currently, 97% of browsers support it. Source: icanuse

The <Link /> component can pass on state

React Router's Link component has a to prop which you can provide an object instead of a string. Here's who this looks.

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

We can use state to pass on information to the component that will be rendered. In this example, state is assigned a number, which will suffice in answering your question, you'll see later, but it can be anything. The route /card rendering <Card /> will have access to the variable state now at props.location.state and we can use it as we wish.

Labeling each list item

When rendering the various cards, we add a unique class to each one. This way we have an identifier that we can pass on and know that this item needs to be scrolled into view when we navigate back to the card list overview.

Solution

  1. <Cards /> renders a list, each item with a unique class;
  2. When an item is clicked, Link /> passes the unique identifier to <Card />;
  3. <Card /> renders card details and a back button with the unique identifier;
  4. When the button is clicked, and <Cards /> is mounted, .scrollIntoView() scrolls to the item that was previously clicked using the data from props.location.state.

Below are some code snippets of various parts.

// 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);
like image 139
Roy Scheffers Avatar answered Sep 18 '22 14:09

Roy Scheffers