Я пытаюсь создать ленту вещей, которая обновляется, когда вы проводите пальцем вниз. Моя логика заключается в том, что мне нужно получить необходимые данные перед рендерингом канала, и я буду получать новые данные каждый раз, когда нажимаю 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));
возможно другие проблемы вне этого компонента, не только перерендеренные (по какой причине?), но и созданные и удаленные? (отображено без key
в компоненте смахивания?) ... данные в состоянии не нужны даже для fetchMore
, см. Документы по разбивке на страницы аполлона
Я бы сказал, что перелистывание не предназначено для извлечения большего [вариант использования], поиска другого инструмента рендеринга виртуального списка (отображаемого внутри) [с поддержкой перелистывания]
@xadm Я хотел использовать framer-motion для прокрутки страницы, но были зависания из-за изменения состояния при пролистывании (не характерно для движения кадра, а из-за некоторых дополнительных изменений состояния, которые мне нужно было внести). Смотрите codeandbox.io/s/framer-motion-pages-shmd9. Просмотры с возможностью перелистывания сработали для меня лучше, и я буду вызывать больше запросов с помощью функции onTransitionEnd
.
Также для большей ясности - разбиение на страницы может работать неправильно, потому что я извлекаю данные случайным образом, поэтому они не по порядку.
Вам не нужно использовать состояние. Просто используйте данные запроса
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));
Спасибо, но меня не беспокоят полученные данные, меня беспокоит тот факт, что запрос постоянно вызывается, хотя мне не нужно, чтобы он вызывался при каждом повторном рендеринге. Я обновил свой основной пост скриншотами, чтобы проиллюстрировать, что именно я имею в виду. Мне нужно сохранить данные моего запроса в состоянии, потому что я буду добавлять к ним, и поэтому мне нужно, чтобы они знали, какие данные были в нем ранее. Мне просто нужно, чтобы он запускался один раз перед самым первым рендером, а затем я буду получать больше данных в отдельных функциях.
Я обновил свой ответ в соответствии с вашим вопросом.
Мне пришлось изменить его на if (data) setFeedArray([...data.getFeedArray])
, а второй аргумент useEffect() должен был быть [data]
, поскольку data
имеет значение null/undefined во время загрузки запроса. У меня есть вопрос: useQuery() вызывается только один раз? Когда я console.info(loading)
над оператором if (loading)
, он возвращает true только при первом рендеринге, а впоследствии всегда false.
правило хуков (без условных хуков) нарушено, вы можете заблокировать дальнейшие вызовы, используя опцию
skip
... но нет необходимости дублировать данные из запроса в состояние