Вызов хука только один раз перед визуализацией компонента (Meteor/React/Apollo/Graphql)

Я пытаюсь создать ленту вещей, которая обновляется, когда вы проводите пальцем вниз. Моя логика заключается в том, что мне нужно получить необходимые данные перед рендерингом канала, и я буду получать новые данные каждый раз, когда нажимаю n-й элемент, когда я пролистываю страницу. Поэтому я подумал о том, чтобы иметь логическую переменную состояния для обнаружения первого рендеринга, а затем установить для нее значение false, чтобы не запускать запрос снова.

const Feed = (props) => {
    const [feedArray, setFeedArray] = useState([]);
    const [firstRender, setFirstRender] = useState(true);
    const FEED_QUERY = gql`
      query getFeedArray {
        getFeedArray
      }
    `;

    if (firstRender) {
      const { loading, error, data } = useQuery(FEED_QUERY);
      if (loading) return <Loading/>;
      if (error) return `Error! ${error}`;
      feedArray.push(...data.getFeedArray);
      setFirstRender(false);
    }

    return (
      <RenderredComponent />
    );
}
export default withApollo(withRouter(Feed));

Понятно, что это приводит к ошибке (Unexpected Error: Rendered fewer hooks than expected. This may be caused by an accidental early return statement.), поскольку хук useQuery вызывается только один раз в операторе if.

Я экспериментировал с разными способами сделать это, так как мне нужны эти данные перед визуализацией моего компонента. Я пробовал useEffect() с props.client.query(), но это не сработало, так как это позволяет выполнять gql-запрос синхронно и, следовательно, только обновляет DOM данными при втором рендеринге, и я не могу заблокировать рендеринг на основе состояния loading. Я не могу поместить useQuery() в useEffect(), так как это тоже выдает ошибку (хук внутри хука).

Если я помещу хук useQuery вне оператора if, он сработает, но запускается при каждом рендеринге, что является ненужным дополнительным вызовом. Для контекста: рендеринг происходит часто, когда я прокручиваю вниз из-за того, как работает React Swipeable Views.

Проще говоря, как я могу запустить хук useQuery только один раз, до того, как компонент будет отрендерен?

Любая помощь приветствуется, спасибо!

Обновлено: Скриншоты ниже, чтобы проиллюстрировать, что происходит,

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

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

EDIT2: принятый ответ работает. Но у меня было неправильное представление о том, как работает useQuery, я должен был вести журнал на сервере всякий раз, когда вызывался запрос. Это вызывает запрос один и только один раз, несмотря на все обновления DOM. Кроме того, он добавляет его в мое состояние только один раз, потому что после первого успешного рендеринга состояние firstRender устанавливается в false. Не уверен, что это идеально, но это то, что я бы сделал, если бы подумал о регистрации вызовов на своем сервере.

const Feed = (props) => {
    const [feedArray, setFeedArray] = useState([]);
    const [firstRender, setFirstRender] = useState(true);
    const FEED_QUERY = gql`
      query getFeedArray {
        getFeedArray
      }
    `;

    const { loading, error, data } = useQuery(FEED_QUERY);
    if (loading) return <Loading/>;
    if (error) return `Error! ${error}`;
    
    if (firstRender) {
      feedArray.push(...data.getFeedArray);
      setFirstRender(false);
    }

    return (
      <RenderredComponent />
    );
}
export default withApollo(withRouter(Feed));

правило хуков (без условных хуков) нарушено, вы можете заблокировать дальнейшие вызовы, используя опцию skip ... но нет необходимости дублировать данные из запроса в состояние

xadm 26.12.2020 18:47

возможно другие проблемы вне этого компонента, не только перерендеренные (по какой причине?), но и созданные и удаленные? (отображено без key в компоненте смахивания?) ... данные в состоянии не нужны даже для fetchMore, см. Документы по разбивке на страницы аполлона

xadm 26.12.2020 19:23

Я бы сказал, что перелистывание не предназначено для извлечения большего [вариант использования], поиска другого инструмента рендеринга виртуального списка (отображаемого внутри) [с поддержкой перелистывания]

xadm 26.12.2020 19:31

@xadm Я хотел использовать framer-motion для прокрутки страницы, но были зависания из-за изменения состояния при пролистывании (не характерно для движения кадра, а из-за некоторых дополнительных изменений состояния, которые мне нужно было внести). Смотрите codeandbox.io/s/framer-motion-pages-shmd9. Просмотры с возможностью перелистывания сработали для меня лучше, и я буду вызывать больше запросов с помощью функции onTransitionEnd.

Ajay Pillay 26.12.2020 19:34

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

Ajay Pillay 26.12.2020 20:22
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
1
5
1 291
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вам не нужно использовать состояние. Просто используйте данные запроса

const Feed = props => {
  const FEED_QUERY = gql`
    query getFeedArray {
      getFeedArray
    }
  `;

  const { loading, error, data } = useQuery(FEED_QUERY);

  if (loading) return <Loading />;
  if (error) return `Error! ${error}`;

  return <RenderredComponent data = {data.getFeedArray} />;
};
export default withApollo(withRouter(Feed));

Вы можете использовать data везде, где хотите.

ОБНОВЛЯТЬ

Если вы хотите использовать состояние по какой-либо причине, вы можете использовать его с useEffect. Добавьте data.getFeedArray к зависимостям useEffect, чтобы при его изменении можно было установить состояние feedArray.

const Feed = props => {
  const [feedArray, setFeedArray] = useState([]);
  const FEED_QUERY = gql`
    query getFeedArray {
      getFeedArray
    }
  `;

  const { loading, error, data } = useQuery(FEED_QUERY);
  useEffect(() => {
    setFeedArray([...data.getFeedArray])
  }, [data.getFeedArray])

  if (loading) return <Loading />;
  if (error) return `Error! ${error}`;

  return <RenderredComponent data = {feedArray} />;
};
export default withApollo(withRouter(Feed));

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

Ajay Pillay 26.12.2020 19:07

Я обновил свой ответ в соответствии с вашим вопросом.

baymax 26.12.2020 19:14

Мне пришлось изменить его на if (data) setFeedArray([...data.getFeedArray]), а второй аргумент useEffect() должен был быть [data], поскольку data имеет значение null/undefined во время загрузки запроса. У меня есть вопрос: useQuery() вызывается только один раз? Когда я console.info(loading) над оператором if (loading), он возвращает true только при первом рендеринге, а впоследствии всегда false.

Ajay Pillay 26.12.2020 19:24

Другие вопросы по теме