Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot refetch a query that has not been started yet #4375

Open
sanduluca opened this issue Apr 29, 2024 · 5 comments
Open

Cannot refetch a query that has not been started yet #4375

sanduluca opened this issue Apr 29, 2024 · 5 comments

Comments

@sanduluca
Copy link

sanduluca commented Apr 29, 2024

We have some crashes reported in our app with the message "Cannot refetch a query that has not been started yet.". We see the crashed in Firebase Crashlytics. I could not repeat the the crash myself. I searched here the issues with the same problem, but could not find a possible solution . The closest related issue seems to be #2889

Here is the crash stats for Android (we have the same and for iOS)
image

Here is the screen code example we have:

Example Screen 1

import { useState } from "react";
import { ScrollView } from "react-native";
import Barcode from "react-native-barcode-builder";
import { Layout, Header } from "components";

import RefreshControl from "components/core/RefreshControl";
import ErrorDataLoad from "components/ErrorDataLoad";
import { useGetInfoQuery } from "store/info";

export function Screen() {
  const [isRefreshing, setIsRefreshing] = useState(false);
  const { data, isLoading, error, refetch } = useGetInfoQuery(
    undefined,
    {
      refetchOnMountOrArgChange: true,
    }
  );

  const onRefresh = () => {
    setIsRefreshing(true);
    refetch().finally(() => setIsRefreshing(false));
  };

  return (
    <Layout.Screen>
      <Header title="" />
      <ScrollView
        contentContainerStyle={{ flex: 1 }}
        refreshControl={
          <RefreshControl refreshing={isRefreshing} onRefresh={onRefresh} />
        }
      >
        {isLoading ? (
          <LoadingOverlay />
        ) : error ? (
          <ErrorDataLoad
            retry={onRefresh}
            message={error.message || ""}
            isRefreshing={isRefreshing}
          />
        ) : data ? (
          <Layout.Block flex={1} align="center">
            <Barcode
              value={barCodeValue}
              format="CODE128"
              text={barCodeValue}
            />
          </Layout.Block>
        ) : null}
      </ScrollView>
    </Layout.Screen>
  );
}

Example Screen 2

import { useState } from "react";
import { FlatList, View } from "react-native";

import { RatingCell, Layout, Header } from "components";
import RefreshControl from "components/core/RefreshControl";
import { useGetRatingGroupsQuery, useGetRatingQuery } from "store/rating";
import { LoadingOverlay } from "components/LoadingOverlay";
import ErrorDataLoad from "components/ErrorDataLoad";

export function RatingScreen() {
  const {
    data: rating,
    error: ratingError,
    isLoading: isLoadingRating,
    refetch: refetchRating,
  } = useGetRatingQuery(undefined, { refetchOnMountOrArgChange: true });
  const {
    data: ratingGroups,
    error: ratingGroupError,
    isLoading: isLoadingRatingGroup,
    refetch: refetchRatingGroup,
  } = useGetRatingGroupsQuery(undefined, { refetchOnMountOrArgChange: true });

  const [isRefreshing, setIsRefreshing] = useState(false);

  const onRefresh = () => {
    setIsRefreshing(true);
    Promise.all([refetchRating(), refetchRatingGroup()]).finally(() => {
      setIsRefreshing(false);
    });
  };

  const error = ratingError || ratingGroupError;
  const isLoading = isLoadingRating || isLoadingRatingGroup;

  return (
    <Layout.Screen>
      <Header title="" />
      {isLoading ? (
        <LoadingOverlay />
      ) : error ? (
        <View style={{ paddingVertical: 16, flex: 1 }}>
          <ErrorDataLoad
            retry={onRefresh}
            message={error.message || ""}
            isRefreshing={isRefreshing}
          />
        </View>
      ) : rating && ratingGroups && ? (
        <>
          <FlatList
            data={ratingList}
            style={{ paddingHorizontal: 20 }}
            renderItem={({ item }) => <RatingCell {...item} />}
            refreshControl={
              <RefreshControl refreshing={isRefreshing} onRefresh={onRefresh} />
            }
          />
        </>
      ) : null}
    </Layout.Screen>
  );
}

@phryneas
Copy link
Member

phryneas commented Apr 29, 2024

We still don't have a reproduction on this. My take is that you somehow save a reference to the refetch method of the first few renders instead of calling refetch on the latest result - you are likely working with a stale closure somewhere.

@sanduluca
Copy link
Author

I notice one common thing with #2889. We are using lodash debounce library with refetch. Here is the example of how we use it.
We define a runDebouncedRetry function that is the result of lodash debounce outside of react context to keep a stable reference. runDebouncedRetry receives a func as a parrameter that is invoked immediately.

import React from "react";
import { View } from "react-native";
import debounce from "lodash/debounce";
import { Button, Typography } from "./core";

const runDebouncedRetry = debounce((func: () => void) => func(), 1000, {
  leading: true,
});

export default function ErrorDataLoad({
  retry,
  message,
  debounceEnabled = true,
  isRefreshing,
}) {
  return (
    <View style={{ flex: 1 }}>
      <View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
        <View style={{ marginTop: 30 }}>
          <Typography variant="accent24">Ups. something happend</Typography>
        </View>
        <View style={{ marginTop: 22 }}>
          <Typography variant="regular14" color="n700">
            {message}
          </Typography>
        </View>
      </View>

      <View style={{ paddingHorizontal: 20 }}>
        <Button
          loading={isRefreshing}
          label={"Retry"}
          type="Main"
          onPress={() => {
            if (debounceEnabled) {
              return runDebouncedRetry(retry);
            }
            return retry();
          }}
        />
      </View>
    </View>
  );
}

@kristiandupont
Copy link

In case this helps someone, we had a situation where we would conditionally supply skipToken to our query but still use the returned refetch function. This used to work (as in, do nothing), but after upgrading to v2, it started giving us this error.

@markerikson
Copy link
Collaborator

@kristiandupont that's very surprising - we made no changes to any of this behavior in v2

@kristiandupont
Copy link

Hm, interesting! I guess that means that I need to double-check that some other behavior isn't broken!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants