skip to Main Content

To get certain data, I need to call multiple custom hooks, all of which are making api requests to get some data, then after each call, I need to use gotten data to pass to the next custom hook to get the next data.

  • If I put the all the calls except the first one into a useEffect hook and the needed data into the dependency array this will give me an error as I cant call custom hooks conditionally inside an effect.
  • Or should I call the first one inside a useEffect, then storing result in state and then just have a simple if statement, so if there is state, call the other hooks
const useSomeCustomData = ({ids}) => {
  const { data: firstData } = useFirstData({
    ids: ids,
  });
  const investmentIds = firstData?.investments.map(
    (investment) => investment?.id
  );
  //next one depending on previously gotten investmentIds
  const { data: secondData } = useSecondData({
    investmentId: investmentIds ? investmentIds : undefined,
  });
  const start = secondData?.products?.map((product) => {
    return product.date;
  })[0];
   //next one depending on previously gotten "start" from secondData
  const { data: thirdData } =
    useThirdData({
      startDate: start,
    });
}

2

Answers


  1. As built-in hooks, custom hooks are also part of a component’s body. It takes part in rendering a component. Therefore it is illegal to invoke a built-in or a custom hook in the handler of the useEffect hook. As we know, the handler of this hook runs only after rendering the component.

    One of the most elementary custom hooks which wraps a network fetch may be written in the pattern as “feed the url and get the data out”. The following custom hook works on that basis.

    // note: error handling has not been included here for brevity
    function useData(url) {
       const [data, setData] = useState(null);
         useEffect(() => {
         if (url) {
           let ignore = false;
           fetch(url)
             .then((response) => response.json())
             .then((json) => {
               if (!ignore) {
                 setData(json);
               }
             });
           return () => {
             ignore = true;
           };
         }
       }, [url]);
      return data;
    }
    

    When the above custom hook is used to retrieve a set of related data, we can chain the hooks as the below code does. It means the output of one hook is fed to another and so on. The custom hook useSomeCustomData below chains the three elementary data hooks and get the final data. The most important aspect in the below code is the “reactive value” url used in the “dependency array” of the three hooks, whenever the url will change with respect to the query strings, then the chain of hooks will automatically restart to execute.

    App.js

    import { useEffect, useState } from 'react';
    
    export default function App() {
      // the state query is not part of the basic requirement,
      // it just provides the options to fetch again.
      const [query, setQuery] = useState(Math.random());
      const someCustomData = useSomeCustomData(query);
      return (
        <>
          Some data : {someCustomData ? someCustomData : 'Loading..'}
          <br />
          <button onClick={() => setQuery((query) => Math.random())}>
            Fetch again
          </button>
        </>
      );
    }
    
    const useSomeCustomData = (query) => {
      // the chain of hooks
      const firstData = useData(
        query ? `/api/firstdata?queryfield=${query}` : null
      );
      const secondData = useData(
        firstData ? `/api/seconddata?queryfield=${firstData}` : null
      );
      const thirdData = useData(
        secondData ? `/api/thirddata?queryfield=${secondData}` : null
      );
      return thirdData;
    };
    
    function useData(url) {
      const [data, setData] = useState(null);
    
      // actual fetch statement below has been mocked with setTimeout async 
      // call, again for brevity.
      useEffect(() => {
        // the below statement setData(null) is required to reset the 
        // state on changing the url, otherwise the string 'Loading' 
        // will not display, even if the final data will come 
        // correctly.
        setData(null);
        const timer = setTimeout(
          () => setData(url ? url.substring(url.indexOf('=') + 1) : null),
          2000
        );
        return () => {
          clearTimeout(timer);
        };
      }, [url]);
      return data;
    }
    

    Test run

    On initial load

    Browser display - On initial load of the app

    After the mocked async calls

    Browser display - after the mocked async calls

    On clicking Fetch again button

    Browser display - On clicking Fetch again button

    After the mocked async calls

    Browser display - after the mocked async calls

    Login or Signup to reply.
  2. This is not a hooks problem, its a design pattern issue.

    If you have a bunch of changed queries which need to fetch some data, wrap those in an async function and use that queryFn in a useEffect.

    Chaining of queries is just a .then chain on first fetch.

    You can have multiple states wired up to your queryFn and have setState calls in each step.

    Instead of using your hook multiple times, move the query chain inside that hook, and use it once.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search