Search code examples
javascriptreactjstypescriptreact-hooksnative

Call React hook depending on Promise


I have an ID stored in AsyncStorage and depending on this call, I want to make a server request.

So something like this is needed:

AsyncStorage.getID().then(id => {
   useLoadFromServer(id)
}

But I always struggle with getting errors about using hooks wrong. So far I tried:

// First Approach
const MyScreen = () => {
  const [ID, setID] = useState()

  AsyncStorage.getID()
    .then(id => setID(id))
    .catch(e => console.log(e))
  
  const { data } = useLoadRoot(ID) // Not the ID from AsyncStorage is used
}
//Second Approach
const MyScreen = async () => {
  const [ID, setID] = useState()

  const IDPromise = AsyncStorage.getID()
  const { data } = useLoadRoot(await IDPromise) // Possible Unhandled Promise Rejection

also I tried to use useEffect, which leads to React Hooks must be called in a React function component or a custom React Hook function

The useLoadRoot-hook just calls another hook UseLoadFromEndpoint doing the axios.get()

When trying to move the AsyncStorage-Request to the customHook, I get the same errors, as I call the useLoadFromEndpoint-hook incorrectly, but I have to / want to reuse the UseLoadFromEndpoint-Hook. How can I achive this?


Solution

  • You need to either modify the useLoadRoot hook or wrap that logic in a child component. Hooks don't allow conditional usage.

    Route 1: Rewrite useLoadRoot

    const useLoadRoot = (id) => {
      const getRoot = async (rootId) => ...;
      const [root, setRoot] = useState();
    
      useEffect(() => {
        if (id != undefined) { 
           await getRoot(id);
        }
      }, [id]);
    
      return root;
    }
    

    This is just one way to achieve what you want. You can pass in an enabled property that is a boolean that allows requests only if true. This is the approach that is used in react-query, for example (see: docs).

    Route 2: Conditional child component

    const Parent = () => {
      const [ID, setID] = useState()
      
      useEffect(() => {
        AsyncStorage.getID()
          .then(id => setID(id))
          .catch(e => console.log(e))
      }, []);
    
      return ID ? <WithFetchRoot rootId={ID}/> : null; //could be a loader component instead of null
    }
    
    const WithFetchRoot = (id) => {
      const root = useLoadRoot(ID);
    
      ...
    }