0

I've implemented a custom React hook creates a task on my API, stores the task UUID in state and then starts polling the API every 2 seconds until successful data is received.

The problem I'm facing is that I want to display the last successful data returned from the API while it's fetching or refetching for new data. Right now the query result is undefined when mutate is called. I don't want that.

The API is slow right now and it takes about 12 seconds or 6 refetches to complete the request successfully, so I want to display the old data meanwhile. I think you understand what I'm trying to say by now.

You can ignore the sendToBackground(), it's how you communicate with background service workers using Plasmo.

Tools I'm using:

  • Plasmo 0.81.0
  • React 18.2.0
  • React Query ^3.39.3

use-suggestions.ts

export function useSuggestions(
  text: string,
  id: string,
): UseQueryResult<TaskTextStatus> {
  const [task, setTask] = useState<string>();
  const [stopRefetch, setStopRefetch] = useState(false);
  const [textDebounced] = useDebounce(text, 1000);

  // Triggers on input text change, calls mutate once per 1000ms
  useEffect(() => {
    mutate(text);
  }, [textDebounced]);

  // Submits text task to API, stores task UUID in `task` state
  const { mutate } = useMutation(
    ["suggestions", id, text],
    async (text: string) => {
      if (!text) return;
      const res = await sendToBackground<{ text: string }, TaskTextSubmit>({
        name: "send-text",
        body: { text },
      });
      return res;
    },
    {
      onSuccess(data) {
        if (!data) return;

        // Sets the created task UUID
        setTask(data.task_uuid);
      },
      onError(e) {
        console.log(e, "ERROR");
      },
    },
  );

  // Activates when task_uuid is known(when task is created)
  // Polls the API every 2 seconds in the background
  // Stops when receives result
  return useQuery(
    ["suggestions", id, text],
    async () => {
      if (!task) return;
      setStopRefetch(false);
      const res = await sendToBackground<{ task_uuid: string }, TaskTextStatus>(
        { name: "check-task", body: { task_uuid: task } },
      );

      if (res.is_success) return res;
    },
    {
      enabled: !!task,
      refetchInterval: stopRefetch ? false : 2000,
      keepPreviousData: true,
      refetchIntervalInBackground: true,
      refetchOnWindowFocus: false,
      onSuccess(data) {
        if (data?.is_success) {
          setStopRefetch(true);
          setTask("");
        }
      },
      onError() {
        setStopRefetch(true);
      },
    },
  );
}

With keepPreviousData addded, it returns the old data only after the first fetch. Then it sets to undefined because no data was returned from the following refetch.

Essentially, I need to keep the last data where is_success was true, up until receiving new data where is_success is also true, keeping out all the whatnot happens in the middle while refetching.

Hope I didn't make it too confusing, thanks for the help!

Devkey
  • 51
  • 1
  • 5
  • 2
    Adding a lot of extra complexity to support having an inconsistent or unreliable api is probably less efficient than fixing your api. Also I'd consider calling a mutation function in a useEffect an anti-pattern which is further indication that you're working against your tools rather than with them. – Chad S. Aug 04 '23 at 17:51

1 Answers1

1

Some feedback:

  • you should throw an error when the fetch is unsuccessful
  • data is not cleared just because the most recent fetch errored, so display data to user even if error exists
  • keepPreviousData: true does not solve your problem, it means the data will be kept during fetching when the query key changes. So in your example, if id changes from 1 to 2, then data from 1 will be displayed to user while 2 is fetching. Consider if you really need it.
  • remove stopRefetch from state. If you want really want to pause or disable a query then set enabled to false

Your updated queryFn can look like this:

async () => {
  const res = await sendToBackground<{ task_uuid: string }, TaskTextStatus>(
    { name: "check-task", body: { task_uuid: task } },
  );

  if (!res.is_success) { throw new Error('Fetch not successful') }

  return res;
},
Ro Milton
  • 2,281
  • 14
  • 9