3

Hi I am new to RTK Query and Redux toolkit. I am working on a pet project where I am trying to poll a workflow status API until it gives me a workflow status as 'CLOSED' Now I am not sure how to poll this API within my component since I would have done that within a useEffect but react doesn't allow hooks within hooks so I can not use my useAPIQuery RTK-Query hooks within useEffect.

example apiSlice.ts -

const getBaseQuery = async (apiPromise: Promise<ServiceModel>) => {
  try {
    const response = await apiPromise;
    if (response) {
      return {
        data: response,
      };
    }
    throw new Error('No data received from service');
  } catch (err) {
    return {
      error: err,
    };
  }
};

export const api = createApi({
  reducerPath: 'api',
  baseQuery: getBaseQuery,
  endpoints: (builder) => ({
    runWorfklow: builder.query({
      query: ({ buildingId, deviceIds }) => getRunWorkflowPromise({ buildingId, deviceIds }),
      transformResponse: (response: RunWorkflowOutput) => ({ ...response }),
    }),
    getWorkflowStatus: builder.query({
      query: ({ buildingId, runId }) => getWorkflowStatusPromise({ buildingId, runId }),
      transformResponse: (response: GetWorkflowStatusOutput) => ({ ...response }),
    }),
  }),
});

// Export hooks for usage in functional components, which are
// auto-generated based on the defined endpoints
export const { useRunWorfklowQuery, useGetWorkflowStatusQuery } = api;

example component -


const LoaderBox = (props: LoaderBoxProps) => {
  const { buildingId, devicesId } = props;

  const {
    data: runWorkflowData,
    error: runWorkflowError,
    isLoading: isRunWorkflowLoading,
    isSuccess: isRunWorkflowSuccess,
  } = useRunWorfklowQuery({
    buildingId,
    devicesId,
  });

  // This call works perfectly fine and give me runStatusData.runStatus as 'OPEN', showing it here for demonstrating how the API would work.
  const {
    data: runStatusData,
    error: runStatusError,
    isLoading: runStatusLoading,
    isUninitialized: runStatusUninitialized,
  } = useGetWorkflowStatusQuery(
    isRunWorkflowSuccess ? { buildingId, runId: runWorkflowData.runId } : skipToken,
  );

  const [isRunFinished, setIsRunFinished] = useState(false);

  useEffect(() => {
    // To continuously poll for WF status
    const poll = () => {
      const { data } = useGetWorkflowStatusQuery(
        isRunWorkflowSuccess ? { buildingId, runId: runWorkflowData.runId } : skipToken,
      );
      if (data?.runStatus === 'CLOSED') {
        setIsRunFinished(true);
      } else {
        setTimeout(() => {
          poll();
        }, 5000);
      }
    };
    poll();
  }, [isRunWorkflowSuccess, runWorkflowData, buildingId]);

return (
<>
 {!runWorkflowError ? (
  <>
   <Text> Requested workflow run</Text>
   isRunFinished ? <Text>Workflow finished</Text> : <Text>Workflow in progress</Text>
  </>
  )
  : <></>}
 {}
</>
);

Now I can make it work by not using the useGetWorkflowStatus hook and directly using the getWorkflowStatusPromise in my useEffect though I wanted to see if I can still use the RTK query hooks to get this done.

The other thing I tried was to add retry on the endpoints in my apiSlice, but there I am not able to do that because my baseQuery function doesn't accept any retry and if I try to add a retry param to my baseQuery function, createAPI doesn't allow that.

The other thing I tried was to use lazyQuery for initiating the GetWorfklowStatus calls after the runWorkflow call gets complete but that did not work either, it fails with an error - This expression is not callable. Type '[LazyQueryTrigger<QueryDefinition<any, (apiPromise: Promise<ServiceModel>) => Promise<{ data: ServiceModel; error?: undefined; } | { error: unknown; data?: undefined; }>, never, { ...; }, "api">>, UseQueryStateDefaultResult<...>, UseLazyQueryLastPromiseInfo<...>]' has no call signatures.

I'll appreciate any pointers which redirect me into the right direction and happy to provide more information on this if needed.

Thanks

Manthan Jamdagni
  • 894
  • 2
  • 17
  • 31

2 Answers2

1

I think you are close with the skipToken approach. The query hooks already have a polling capability built-in. I'd suggest something close to the following:

const runStatusDataRef = React.useRef();

const {
  data: runStatusData,
  error: runStatusError,
  isLoading: runStatusLoading,
  isUninitialized: runStatusUninitialized,
} = useGetWorkflowStatusQuery(
  { buildingId, runId: runWorkflowData.runId },
  {
    pollingInterval: 5000,
    skip: !isRunWorkflowSuccess || runStatusDataRef.current?.runStatus === 'CLOSED',
  }
);

React.useEffect(() => {
  runStatusDataRef.current = runStatusData;
}, [runStatusData]);

If you need to use skipToken you might get by with just setting the polling interval and passing the additional condition to the ternary expression:

const runStatusDataRef = React.useRef();

const {
  data: runStatusData,
  error: runStatusError,
  isLoading: runStatusLoading,
  isUninitialized: runStatusUninitialized,
} = useGetWorkflowStatusQuery(
  isRunWorkflowSuccess && runStatusDataRef.current?.runStatus !== 'CLOSED'
    ? { buildingId, runId: runWorkflowData.runId }
    : skipToken,
  { pollingInterval: 5000 },
);

React.useEffect(() => {
  runStatusDataRef.current = runStatusData;
}, [runStatusData]);
Drew Reese
  • 165,259
  • 14
  • 153
  • 181
  • This looks like a good approach but TS complains about BlockedScoped variable runStatusData being used before its declaration. – Manthan Jamdagni Apr 10 '23 at 14:16
  • @ManthanJamdagni You could cache `runStatusData` in a React ref and reference this instead. I can update my answer if you need to see it as an example. – Drew Reese Apr 11 '23 at 05:43
  • an updated answer would definitely help, I can accept a working answer. Meanwhile, I'll try to explore the React ref you've mentioned, if I am able to make it work I can edit the answer and accept it too. Appreciate your help with this btw. – Manthan Jamdagni Apr 11 '23 at 21:49
  • 1
    @ManthanJamdagni Added React ref example. You'll need to type the ref value if you are using Typescript. If you let me know what `data` values are I can refine this a bit more. – Drew Reese Apr 11 '23 at 21:54
  • 1
    It worked! You can add a type `T` just to demonstrate but I think its good enough. Thanks anyway your help is much appreciated. I also had a doubt around if redux still keeps executing the condition check after the condition is met, but I read about it and seems like it stops checking when the skip token condition is met, so there is no extra work being done. – Manthan Jamdagni Apr 11 '23 at 22:40
0

This may or may not be the best solution for your use case, but just something I've found in one or two occasions along these lines with libraries like RTK Query and react-query:

You still always have the option of falling back to a regular ajax request with axios or Fetch API. At the end of the day if it solves your product requirements easier and faster- just do that, and refactor later if needed (which you 99% guaranteed will never need to do) would be my suggestion. Just another frame to try out anyway, good luck!

lt1
  • 761
  • 5
  • 11