Предотвратить переход страницы при нажатии кнопки? РеактJS

У меня есть компонент ShowcaseMovie, который извлекает данные о componentDidMount() и устанавливает их в состояние. Компонент отображает компоненты Card для отображения данных, а также четыре элемента кнопок upcoming, top_rated, popular и now_playing, которые позволяют пользователю переключаться между соответствующими данными. Каждая кнопка имеет событие onClick, которое вызывает changeFilter и устанавливает состояние currentFilter для выбранной клавиши.

Проблема: когда нажимаются кнопки фильтра, иногда страница перескакивает наверх (если ее еще нет). Я пытался найти решения для этого, но я не могу понять, что происходит. Любые предложения будут большим подспорьем, спасибо заранее.

Обновление: эта проблема возникает, когда для элемента с динамическими дочерними элементами не задана высота. Если я установлю высоту ShowcaseMovie на что-то большое, например height: 200vh, она исчезнет.

Я считаю, что решил свою проблему, но хотел бы услышать другие мысли о том, почему это происходит, и о некоторых других способах это исправить. Трудно установить высоту для родителя, когда вы не знаете, сколько контента будет отображаться (или высота этого контента). min-height помогло бы, но все же быстрое решение.

ShowcaseMovie.js

import React, { Component } from "react";
import Card from "./Card";
import "../css/ShowcaseMovie.css";
import { v4 as uuidv4 } from "uuid";
import { formatString, buildMovieState } from "../utilities";

class ShowcaseMovie extends Component {
  static defaultProps = {
    filterNames: ["upcoming", "popular", "now_playing", "top_rated"]
  };

  constructor(props) {
    super(props);
    this.state = {
      upcoming: [],
      now_playing: [],
      popular: [],
      top_rated: [],
      currentFilter: this.props.filterNames[0]
    };
  }

  changeFilter = e => {
    e.preventDefault();
    const type = e.target.name;
    this.setState({ currentFilter: type });
  };

  componentDidMount() {
    this.props.filterNames.map(name => this.fetchMovies(name));
    // setInterval(() => {
    //   this.timeoutFilter();
    // }, 10000);
  }

  async fetchMovies(type) {
    try {
      const res = await fetch(
        `url`
      );
      const data = await res.json();
      if (data) {
        this.setState(state => ({
          ...state,
          [type]: buildMovieState(data)
        }));
      }
    } catch (error) {
      console.log(error);
    }
  }

  render() {
    const { currentFilter } = this.state;
    const movies = this.state[currentFilter].map((movie, i) => (
      <Card key={uuidv4()} movie={movie} index={i} />
    ));
    const buttons = this.props.filterNames.map(name => (
      <button
        type="button"
        key={name}
        name={name}
        className={`ShowcaseMovie-btn ${
          currentFilter === name ? "active" : ""
        }`}
        disabled={currentFilter === name}
        onClick={this.changeFilter}>
        {formatString(name)}
      </button>
    ));

    return (
      <section className="ShowcaseMovie">
        <div className="ShowcaseMovie-container">
          <h2 className="ShowcaseMovie-header">Movies</h2>
          <div className="ShowcaseMovie-btn-container">{buttons}</div>
        </div>
        <div className="ShowcaseMovie-grid">{movies}</div>
      </section>
    );
  }
}

ShowcaseMovie.css

.ShowcaseMovie {
  padding: 4rem 10%;
}

.ShowcaseMovie-container {
  position: relative;
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 2rem;
}

.ShowcaseMovie-button-container {
  display: flex;
  justify-content: center;
  align-items: center;
}

.ShowcaseMovie-container::after {
  content: "";
  background-color: #64b5f6;
  height: 80%;
  width: 6px;
  left: 0;
  position: absolute;
  border-radius: 1px;
}

.ShowcaseMovie-header {
  font-size: 3rem;
  font-weight: 200;
  margin: 0 5rem;
}

.ShowcaseMovie-btn {
  outline: none;
  border: none;
  background-color: transparent;
  font-size: 1.6rem;
  font-weight: 500;
  letter-spacing: 1px;
  padding: 1rem;
  margin-left: 4rem;
  color: white;
  opacity: 0.5;
  cursor: pointer;
  transition-property: opacity;
  transition-duration: 300ms;
  transition-timing-function: ease;
}

.ShowcaseMovie-btn:hover {
  opacity: 1;
  transition-property: opacity;
  transition-duration: 300ms;
  transition-timing-function: ease;
}

.ShowcaseMovie-btn.active {
  opacity: 1;
  cursor: auto;
  color: #64b5f6;
}

.ShowcaseMovie-grid {
  display: grid;
  gap: 3rem;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
}

Card.js

import React, { Component } from "react";
import "../css/Card.css";

class Card extends Component {
  render() {
    const { title, poster_path } = this.props.movie;
    const style = { animationDelay: `${80 * this.props.index}ms` };
    return (
      <div className="Card">
        <div className="Card-inner" style={style}>
          <img
            src={`https://image.tmdb.org/t/p/w500/${poster_path}`}
            alt=""
            className="Card-img"
          />
          <p className="Card-name">{title}</p>
        </div>
      </div>
    );
  }
}

export default Card;

Card.css

.Card {
  display: block;
  transition: transform 300ms ease;
}

.Card:hover {
  transform: translateY(-5px);
  transition: transform 300ms ease;
}

.Card-inner {
  position: relative;
  display: block;
  cursor: pointer;
  height: 100%;
  opacity: 0;
  animation-name: moveUp;
  animation-duration: 500ms;
  animation-delay: 50ms;
  animation-timing-function: ease;
  animation-fill-mode: forwards;
}

.Card-inner::after {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-image: linear-gradient(transparent, rgba(33, 47, 61, 0.8));
  border-bottom-left-radius: 2px;
  border-bottom-right-radius: 2px;
  z-index: 100;
  opacity: 1;
  transition: opacity 300ms ease;
}

.Card-inner:hover::after {
  opacity: 0;
  transition: opacity 300ms ease;
}

.Card-inner::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-image: linear-gradient(transparent, rgba(100, 180, 246, 0.6));
  border-bottom-left-radius: 2px;
  border-bottom-right-radius: 2px;
  z-index: 100;
  opacity: 0;
  transition: opacity 300ms ease;
}

.Card-inner:hover::before {
  opacity: 1;
  transition: opacity 300ms ease;
}

.Card-img {
  display: block;
  position: relative;
  object-fit: cover;
  max-width: 100%;
  min-height: 100%;
  z-index: 0;
  border-radius: 2px;
}

.Card-name {
  position: absolute;
  bottom: 0;
  left: 0;
  margin: 0 2rem 2rem 2rem;
  z-index: 150;
  font-weight: 400;
  text-transform: uppercase;
  font-size: 1.4rem;
  letter-spacing: 2px;
}

@keyframes moveUp {
  0% {
    transform: translateY(5rem);
  }

  100% {
    transform: translateY(0);
    opacity: 1;
  }
}

utilities.js

export const formatString = name => {
  return name
    .replace("_", " ")
    .split(" ")
    .map(w => w[0].toUpperCase() + w.slice(1))
    .join(" ");
};

export const buildMovieState = data => {
  if (data.results) {
    const movies = data.results.filter(
      d => d.backdrop_path && d.id && d.title && d.poster_path
    );
    return movies.length > 10 ? movies.slice(0, 10) : movies;
  } else {
    return [];
  }
};


person Kyle Lambert    schedule 25.03.2020    source источник
comment
Пожалуйста, включите также источник компонента карты.   -  person Leon Vuković    schedule 25.03.2020
comment
Привет, Леон, я только что добавил источник компонента Card. Спасибо   -  person Kyle Lambert    schedule 25.03.2020
comment
Для таких случаев, когда возникают проблемы с пользовательским интерфейсом, выход из проекта CodeSandBox — очень хорошее решение. включение кодов только усложняет решение, чем раньше.   -  person AmerllicA    schedule 25.03.2020
comment
Я попытался создать коды и ящик, который использует ваш код. Я добавил некоторые поддельные данные и вижу, как страница подпрыгивает при нажатии на кнопки. Но именно так реагирует браузер: это происходит только тогда, когда новое представление имеет меньшую высоту, чем предыдущее, и сохранение той же прокрутки невозможно, поскольку страница имеет разную глубину. Это та проблема, о которой вы говорите? codesandbox.io/s/affectionate-zhukovsky-fonbc   -  person Nicolas SEPTIER    schedule 25.03.2020
comment
Я только что создал CodeSandbox, а затем добавил разделитель в верхней части страницы, чтобы понять проблему. Если вы прокрутите вниз до компонента ShowcaseMovie (чтобы белый разделитель не отображался), а затем щелкните между кнопками фильтра, вы обнаружите, что он переместится в верхнюю часть страницы. Это происходит каждые 10 или около того кликов.   -  person Kyle Lambert    schedule 25.03.2020
comment
Николя, спасибо за ответ. Я думаю, вы на правильном пути, но моя демонстрация, кажется, прыгает прямо в начало страницы. Поэтому, если у меня есть больше разделов над этим компонентом, он перепрыгнет через них и на самый верх.   -  person Kyle Lambert    schedule 25.03.2020
comment
@KyleLambert, у тебя есть ошибки в консоли?   -  person Leon Vuković    schedule 25.03.2020
comment
Ошибок в консоли нет, вроде пропадает, если я устанавливаю высоту на самом ShowcaseMovie. .ShowcaseMovie { height: 200vh } Есть идеи, почему это так? Спасибо   -  person Kyle Lambert    schedule 25.03.2020


Ответы (1)


Я смог предотвратить это, вернув false из вызова onClick. Как это:

onClick={
   doSomething()
   return false
}
person Davis Jones    schedule 24.11.2020