Проблема с useState в ReactJS и разбиением на страницы - PullRequest
1 голос
/ 19 июня 2020

У меня проблема, которую я не могу исправить с помощью useState React. Я пытаюсь сделать API-запрос фильмов и разбивки на страницы, но одно из моих состояний - undefined, и я действительно не понимаю, почему ... Мой компонент - это функциональный компонент.

Вот мой useState:

const [state, setState] = useState({
    films: [],
    page: 1,
    genres: [],
    currentCategory: ""
  })

Вот мой запрос API:

const getFilms = () => {
    Axios.get(`${baseUrlDiscover}&page=${state.page}&with_genres=${state.currentCategory}`)
      .then(response => {
        setState({
          films: response.data.results
        });
      })
      .catch(error => {
        console.log(error);
        setState({
          films: []
        });
      });
  }

Вызов getFilms() для монтирования компонента.

useEffect(() => {
    getFilms()
  }, [])

И я отображаю полученные фильмы с .map вот так:

const theMovies = state.films.map((film, idx) => {
    return <Film film={film} key={idx} />
  });

На данный момент я получаю 20 фильмов на первой странице, но когда я регистрирую page state в моя консоль для получения второй страницы я получаю undefined. Я хотел бы использовать эту часть кода ниже для получения второй страницы из 20 фильмов, но без page state я получаю сообщение об ошибке типа state.films.map is undefined

const btnClickNext = (e) => {
    if (state.films && state.page !== 500) {
      setState(
        prevState => ({ page: (prevState.page += 1) }),
        getFilms
      );
    }
    getFilms();
  }

И в return я вызываю btnClickNext функция:

<Button
  href=""
  target="_blank"
  onClick={() => btnClickNext()}
>
  Next <FaChevronRight />
</Button>

Есть ли у вас какие-либо идеи, откуда может взяться проблема? и почему console.log(state.page) дает undefined?

Большое спасибо

Ответы [ 2 ]

0 голосов
/ 19 июня 2020

См. это примечание в документации:

Примечание

В отличие от метода setState, найденного в компонентах класса, useState не объединяет объекты обновления автоматически. Вы можете воспроизвести это поведение, объединив форму средства обновления функции с синтаксисом расширения объекта:

setState(prevState => {
  // Object.assign would also work
  return {...prevState, ...updatedValues};
});

Другой вариант - useReducer, который больше подходит для управления объектами состояния, содержащими несколько подзначений.

При текущем способе работы с состоянием, используя один объект для всего своего состояния, вы должны распределить другие аспекты своего состояния, просто обновляя один элемент. Так, например, вместо:

// Incorrect
setState({
    films: []
});

вы должны сделать:

// Incorrect
setState(state => ({
    ...state,
    films: []
}));

Не делать это:

// DON'T DO THIS, IT'S LIKELY TO CAUSE TROUBLE
setState({
    ...state,
    films: []
});

Обновления состояния могут быть асинхронными и могут накапливаться. Если вы сделаете это, вы можете стереть эффект предыдущего обновления.

Но , вместо использования одного объекта состояния, я рекомендую прочитать всю эту страницу . По сути, с хуками вместо:

// Not this
const [state, setState] = useState({
    films: [],
    page: 1,
    genres: [],
    currentCategory: ""
})

вы обычно хотите это:

// This instead
const [films, setFilms] = useState([]);
const [page, setPage] = useState(1);
const [genres, setGenres] = useState([]);
const [currentCategory, setCurrentCategory] = useState("");

Затем используйте соответствующую функцию setXYZ. Вместо setState({films: []}) вы бы сделали:

setFilms([]);

В комментарии вы сказали:

Я сделал useState для каждого элемента и, наконец, получил правильный. Моя функция увеличивает количество страниц, но я не могу получить фильмы с этих страниц.

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

useEffect(() => {
    getFilms(page, currentCategory); // ***
}, [page, currentCategory]);         // ***

Теперь обратный вызов будет вызываться при изменении page или currentCategory (но не беспокойтесь, обычно он вызывается только один раз, если вы меняете их обоих один за другим).

Вы также можете рассмотреть возможность отмены невыполненного GET, если вы начинаете новый. (Или, как минимум, игнорируя его результат.) Подробности см. В документации ios . Из документации похоже, что в нем используется старое предложение отменяемых обещаний, которое сейчас отозвано. Вот версия getFilms, которая принимает токен отмены:

const getFilms = (page, currentCategory, cancelToken) => {
    return Axios.get(
        `${baseUrlDiscover}&page=${page}&with_genres=${currentCategory}`,
        { cancelToken }
    );
};

Тогда вы бы использовали его так:

useEffect(() => {
    let cancel;
    let cancelToken = new Axios.CancelToken(c => {
        cancel = () => {
            c();
            c.cancelled = true;
        };
    });
    getFilms(page, currentCategory, cancelToken)
        .then(films => setFilms(films))
        .catch(error => {
            if (!cancel.cancelled) {
                // ...handle/report error, then:
                setFilms([]);
            }
        });
    return cancel;
}, [page, currentCategory]);

Довольно неуклюжий, может быть, есть топор получше ios -specifi c way (я не использую Ax ios). FWIW, вот современная версия getFilms с использованием fetch и AbortSignal:

const getFilms = (page, currentCategory, signal) => {
    return fetch(

${baseUrlDiscover}&page=${page}&with_genres=${currentCategory}, {signal}); };

Тогда вы бы использовали это так:

useEffect(() => {
    const controller = new AbortController();
    const signal = controller.signal;
    getFilms(page, currentCategory, signal)
        .then(films => setFilms(films))
        .catch(error => {
            if (!signal.aborted) {
                // ...handle/report error, then:
                setFilms([]);
            }
        });
    return () => controller.abort();
}, [page, currentCategory]);

Я рекомендую эту статью на useEffect (на самом деле это больше, чем useEffect, это о том, как работают функциональные компоненты, использующие хуки).

0 голосов
/ 19 июня 2020

На самом деле это не тот способ, которым вы должны использовать useState. Вы должны создать отдельное useState, подобное этому

 const [films, setFilms] = useState([]),
 const [page, setPage] = useState(1),
 const [genres,setGenres] = useState([]),
 const [currentCategory, setCurrentCategory] = useState("")

Везде, где вам нужно обновить эти состояния, вы можете соответственно вызвать соответствующий установщик.

...