salia_in_red
salia_in_red

Reputation: 67

How to return to the same scroll position when going back using useNavigate() or useLocation() react-router

When clicking on a specific pokemon, the user can show details of the pokemon. When the user clicks the back button to see all pokemon, I want them to continue in the exact same scroll position/pokemon, where they first clicked. How to achieve this?

Here is the pokedex component, where the user can click on each pokemon:

import { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { ScrollArrow } from './utils/scrollArrow';
import { Container, Card, Col, Row, Spinner } from 'react-bootstrap';

const Pokedex = () => {
  const [pokemon, setPokemon] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    getPokedex();
  }, []);

  const getPokedex = async () => {
    try {
      const res = await fetch('https://pokeapi.co/api/v2/pokemon?limit=151');
      const { results } = await res.json();
      const pokedex = results.map((pokemon: any, index: number) => {
        const paddedId = ('00' + (index + 1)).slice(-3);
        const image = `https://assets.pokemon.com/assets/cms2/img/pokedex/detail/${paddedId}.png`;
        return { ...pokemon, image };
      });
      setPokemon(pokedex);
      setLoading(false);
    } catch (err) {
      console.error(err);
    }
  };

  return (
    <Container fluid className='pokedex'>
      {loading ? (
        <Spinner animation='border' role='status'>
          <span className='visually-hidden'>Fetching Pokemon...</span>
        </Spinner>
      ) : (
        <Row>
          {pokemon.map((pokemon: any, index: number) => (
            <Col key={index} xs={12} sm={6} lg={4} xl={2} className='col'>
              <Card>
                <Link to={`/pokemon/${index + 1}`}>
                  <Card.Img src={pokemon.image} alt={pokemon.name} />
                  <Card.Body>
                    <Card.Text>
                      #{(index + 1).toString().padStart(3, '0')}
                    </Card.Text>
                    <Card.Title>{pokemon.name}</Card.Title>
                  </Card.Body>
                </Link>
              </Card>
            </Col>
          ))}
        </Row>
      )}
      <ScrollArrow />
    </Container>
  );
};

export default Pokedex;

Here is the pokemon component, where the user can go back to the pokedex to see all pokemon:

import { useEffect, useState } from 'react';
import { colors } from './utils/bgColor';
import {
  Button,
  Col,
  Container,
  Image,
  Row,
  Spinner,
  ListGroup,
  ProgressBar,
  Tab,
  Tabs,
  TabContainer,
} from 'react-bootstrap';
import { useNavigate, useParams } from 'react-router-dom';

const Pokemon = () => {
  const [pokemonDetails, setPokemonDetails] = useState<any>([]);
  const [loading, setLoading] = useState(true);

  const { id } = useParams();

  const getPokemon = async (id: string | undefined) => {
    try {
      const res = await fetch(`https://pokeapi.co/api/v2/pokemon/${id}`);
      const pokemon = await res.json();
      const paddedId = ('00' + id).slice(-3);
      pokemon.image = `https://assets.pokemon.com/assets/cms2/img/pokedex/detail/${paddedId}.png`;
      setPokemonDetails(pokemon);
      setLoading(false);
    } catch (err) {
      console.error(err);
    }
  };

  useEffect(() => {
    getPokemon(id);
  }, [id]);

  let navigate = useNavigate();
  const handleClick = () => {
    navigate('/');
  };

  let typeName = pokemonDetails.types && pokemonDetails.types[0].type.name;
  const bgColor: string = colors[typeName];

  return (
    <Container fluid className='pokemon' style={{ backgroundColor: bgColor }}>
      {loading ? (
        <Spinner animation='border' role='status'>
          <span className='visually-hidden'>Fetching Pokemon...</span>
        </Spinner>
      ) : (
        <div className='details' style={{ position: 'relative' }}>
          <Row>
            <Col className='header'>
              <h1>{pokemonDetails.name}</h1>
              <h3>#{pokemonDetails.id.toString().padStart(3, '0')}</h3>
            </Col>
          </Row>
          <Row>
            <Col>
              <ListGroup className='type'>
                {pokemonDetails.types.map((type: any, index: number) => (
                  <ListGroup.Item key={index}>{type.type.name}</ListGroup.Item>
                ))}
              </ListGroup>
            </Col>
          </Row>
          <Row>
            <Image
              src={pokemonDetails.image}
              alt={pokemonDetails.name}
              className='pokemon-img'
            />
          </Row>
          <TabContainer>
            <Row className='clearfix'>
              <Col sm={12} className='box'>
                <Tabs defaultActiveKey='stats'>
                  <Tab eventKey='abilities' title='Abilities'>
                    <ListGroup>
                      {pokemonDetails.abilities.map(
                        (ability: any, index: number) => (
                          <ListGroup.Item key={index}>
                            {ability.ability.name}
                          </ListGroup.Item>
                        )
                      )}
                    </ListGroup>
                  </Tab>
                  <Tab eventKey='stats' title='Stats'>
                    <ListGroup>
                      {pokemonDetails.stats.map((stat: any, index: number) => (
                        <ListGroup.Item key={index}>
                          {stat.stat.name}
                          <ProgressBar
                            now={stat.base_stat}
                            label={stat.base_stat}
                          />
                        </ListGroup.Item>
                      ))}
                    </ListGroup>
                  </Tab>
                  <Tab eventKey='moves' title='Moves'>
                    <ListGroup className='moves'>
                      {pokemonDetails.moves
                        .slice(0, 62)
                        .map((move: any, index: number) => (
                          <ListGroup.Item key={index}>
                            {move.move.name}
                          </ListGroup.Item>
                        ))}
                    </ListGroup>
                  </Tab>
                  <Tab eventKey='evolutions' title='Evolutions' disabled>
                    {/* <p className='possible evolution'>
      {pokemonDetails.stats.map((type: any, index: number) => (
        <p key={index}>{type.type.name}</p>
      ))}
    </p> */}
                  </Tab>
                </Tabs>
              </Col>
            </Row>
          </TabContainer>
          <Button variant='dark' onClick={handleClick}>
            Catch another Pokémon
          </Button>
        </div>
      )}
    </Container>
  );
};

export default Pokemon;

Upvotes: 1

Views: 12665

Answers (3)

vramazing
vramazing

Reputation: 92

You can check browser history API https://developer.mozilla.org/en-US/docs/Web/API/History#specifications

Or you can get the current scroll position before navigating and then reset it when you return.

window.pageYOffset || document.documentElement.scrollTop - This should get you current scroll position

Upvotes: 0

angel
angel

Reputation: 1

There's an easy way with react-router-dom v6

Back

Your Welcome!

Upvotes: -1

TalOrlanczyk
TalOrlanczyk

Reputation: 1213

I found an answer in this thread: How do people handle scroll restoration with react-router v4?

this is in v4 and TS but still have a good solution for this Hopefully it will help you

Upvotes: 0

Related Questions