Search code examples
reactjsreact-hooksrtk-query

React hooks & RTK pattern for a series of chained http requests


In my component, I need to fetch contestDetails, get a value from the response (draftgroupId), and then fetch the draftGroup. Normally I'd wrap them each in useEffect:

  const [draftGroupId, setDraftGroupId] = useState();
  useEffect(() => {
    api.getContestDetails(contestId).then(res => setDraftGroupId(res.draftGroupId));
  }, []);

  useEffect(() => {
    api.getDraftGroupId(draftGroupId).then(res => /* use the response */);
  }, [draftGroupId]);

However, I'm making my fetches with RTK Query, so fetches themselves are performed with hooks. RTK Query handles when to fetch and when not to (or something like that), so the first query, useGetContestByIdQuery(contestId); can just be at the top level of the component.

The second query, useGetDraftGroupByIdQuery(draftGroupId);, however, needs to wait until we have a contestId. But you can't call a hook conditionally, so I can't wrap it in an if, and you also can't call a hook from another hook, so I can't wrap it in a useEffect that would have contestId as a dependency like the above example.

I'm not sure if this changes things, but I'm also not using the return value of the RTKQ hooks because I'm handling that data in my own RTK reducers (with extraReducers). I don't think that makes a difference, since whether I get draftgroupId from redux or whether I get it from the data return of the query hook, my problem is still the same.

Here is what I came up with, which is simply wrapping each fetch in its own component, since the components will render conditionally.

const GetContest = ({ contestId, ...props }) => {
  useGetContestByIdQuery(contestId); // populates state.currentDraft for next line
  
  const { draftgroupId, gameTypeId } = useSelector(getCurrentDraftState).contest;

  return !draftgroupId ? null : (
    <GetDraftGroup
      draftGroupId={draftgroupId}
      gameTypeId={gameTypeId}
      {...props}
    />
  );
};

const GetDraftGroup = ({ draftGroupId, gameTypeId, ...props }) => {
  useGetDraftGroupByIdQuery(draftGroupId, gameTypeId); // populates state.currentDraft.players for next line

  const players = useSelector(getAllPlayers);

  return !players ? null : <ActualGameScreen {...props} />;
};

This cannot possibly be the right way to do this, right? What is the right way to do this with RTK Query?


Solution

  • You can use the skip option or skipToken for your "conditional" case:

    useGetContestByIdQuery(contestId ?? skipToken);
    

    or

    useGetContestByIdQuery(contestId, { skip: !contestId });