useState создает дубликаты — бесконечная прокрутка — Предупреждение: Обнаружены два дочерних элемента с одним и тем же ключом

#reactjs #react-hooks #react-router-dom #use-state

Вопрос:

Чтобы дать некоторое представление о том, что я пытаюсь заархивировать:

Таким образом, в основном второй эффект использования отображает список навигации, нажатие на элемент списка навигации должно отображать правильный список фильмов жанра, он работал до тех пор, пока я не попытался реализовать бесконечную прокрутку… теперь после нажатия на элемент навигационного списка он всегда добавляет фильмы из предыдущего элемента навигации, что создает дубликаты.

И я получаю следующие ошибки: Предупреждение: Столкнулись с двумя детьми с одним и тем же ключом. Также React говорит мне добавить фильмы в качестве зависимости в первом эффекте использования. Но если я это сделаю, это вызовет бесконечный цикл.

Я думаю, что основная проблема заключается в следующем: const newMovieList = […фильмы, …данные.результаты] и отсутствующая зависимость.

Вероятно, еще одна проблема заключается в том, чтобы в первую очередь добавить эти многочисленные зависимости к эффекту использования?

Я часами пытался это исправить, но всегда возникали какие-то странные побочные эффекты.. как будто один дубликат фильма или какой-то жанр работают, а другие нет.

Любой совет или помощь, как это исправить, были бы великолепны.

 import React, { useEffect, useState } from "react";
import { NavLink, useParams, useRouteMatch } from "react-router-dom";
import Movies from "./Movies";

export default function TestMovie(props) {
  const [loading, setLoading] = useState(false);
  const [genres, setGenres] = useState([]);
  const [movies, setMovies] = useState([]);
  const [pageNumber, setPageNumber] = useState(1);
  const params = useParams();
  const paramsId = params.id;
  const route = useRouteMatch();

  useEffect(() => {
    (async () => {
      setLoading(true);
      try {
        let response = "";

        if (route.path === "/") {
          response = await fetch(
            `https://api.themoviedb.org/3/movie/upcoming?api_key=${apiKey}amp;page=${pageNumber}`
          );
        } else if (paramsId === "23") {
          response = await fetch(
            `https://api.themoviedb.org/3/movie/popular?api_key=${apiKey}amp;page=${pageNumber}`
          );
        } else {
          response = await fetch(
            `https://api.themoviedb.org/3/discover/movie?api_key=${apiKey}amp;with_genres=${paramsId}amp;page=${pageNumber}`
          );
        }

        const data = await response.json();
        const newMovieList = [...movies, ...data.results];
        setMovies(newMovieList);
        console.log("PageNumber: "   pageNumber);

      } catch (error) {
        console.log(error);
      } finally {
        setLoading(false);
      }
    })();
  }, [setMovies, paramsId, route.path, pageNumber]);

  useEffect(() => {
    (async () => {
      try {
        const response = await fetch(
          `https://api.themoviedb.org/3/genre/movie/list?api_key=${apiKey}`
        );
        const data = await response.json();
        const newGenres = [{ id: 23, name: "Popular" }, ...data.genres];
        setGenres(newGenres);
      } catch (error) {
        console.log(error);
      }
    })();
  }, [setGenres]);


  return (
    <>
      <ul className="genre-list">
        <li className="genre-list__item">
          <NavLink exact activeClassName="active" to="/">
            Upcoming
          </NavLink>
        </li>
        {genres.map((genre) => (
          <li className="genre-list__item" key={genre.id}>
            <NavLink
              exact
              activeClassName="active"
              to={`/genre/${genre.id}-${genre.name}`}
            >
              {genre.name}
            </NavLink>
          </li>
        ))}
      </ul>
      <Movies
        setPageNumber={setPageNumber}
        movies={movies}
        loading={loading}
      ></Movies>
    </>
  );
}

 

Часть бесконечного прокрутки:

 export default function Movies(props) {
  const { movies, loading, setPageNumber } = props;

  function updatePageNumber() {
    setPageNumber((pageNumber) => pageNumber   1);
  }

  return loading ? (
    <div className="loader">
      <Loader />
    </div>
  ) : (
    <>
      <InfiniteScroll
        dataLength={movies.length}
        next={updatePageNumber}
        hasMore={true}
      >
        <div className="movies-layout">
          {movies.map((movie) => (
            <Movie key={movie.id} movie={movie}></Movie>
          ))}
        </div>
      </InfiniteScroll>
    </>
  );
}
 

Ответ №1:

Могут быть разные способы выполнения бесконечной прокрутки. Исходя из вашего пути, главная проблема заключается в этой строке

   const newMovieList = [...movies, ...data.results];
 

Вы не можете быть уверены, что новый результат должен быть новым, потому что пользователь может перейти на старую страницу (или изменить настройки «строки на странице»).

Чтобы избежать этого, возможно, самый простой способ для вас-создать функцию для добавления элемента по ключу элемента.

   function getNewMovieList(current, data) {
    const movies = {}
    [...current, ...data].forEach(item => {
      movies[item.id] = item
    })
    return Object.values(movies)
  }
 

Буквально вы хотите убедиться, что это все еще новый список без повторяющихся элементов. Ошибка, которую вы получаете, на самом деле сослужит вам хорошую службу в этом случае.

Другой способ

Другой способ-поместить fetch вызов внутри компонента страницы,

   const Page = ({ pageId }) => {
    const [data, setData] = useState([])
    useEffect(() => { go fetch data for this page ONLY })
  }
 

Теперь , если вы перейдете <Page pageId={5} /> , вы вообще не столкнетесь с проблемой дублирования данных.

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

Комментарии:

1. один вопрос к первому способу: будет ли это не только удалять дубликаты, но и копировать фильмы из текущего массива, которые не являются дубликатами, но также не являются частью жанра, в новый массив ?

2. копия сделана по ссылке, так что это не клон. Кстати, просто попробуйте алгоритм во console вкладке. И как только вы убедитесь, вы сможете его включить.

3. Попробовал, он удалил дубликаты, но смешал все дикое вместе. Я должен сказать, что я новичок, может быть, я сделал это неправильно. Нашел альтернативный способ, но, вероятно, это действительно плохая практика. Я добавил один клик по навигационному элементу, который как бы сбрасывает состояния: функция handleClickRefresh() { setPageNumber(1); фильмы = []; setMovies(фильмы); }