Search code examples
javascriptreactjsreact-nativereact-hooksuse-effect

How to use a custom-hook that returns a value, inside another custom Hook?


I am using React-native and in it, I have a custom Hook called useUser that gets the user's information from AWS Amplify using the Auth.getUserInfro method, and then gets part of the returned object and sets a state variable with it. I also have another Hook called useData hook that fetches some data based on the userId and sets it to a state variable.

useUser custom-Hook:

import React, { useState, useEffect } from "react";
import { Auth } from "aws-amplify";

const getUserInfo = async () => {
  try {
    const userInfo = await Auth.currentUserInfo();
    const userId = userInfo?.attributes?.sub;
    return userId;
  } catch (e) {
    console.log("Failed to get the  AuthUserId", e);
  }
};

const useUserId = () => {
  const [id, setId] = useState("");

  useEffect(() => {
    getUserInfo().then((userId) => {
      setId(userId);
    });
  }, []);

  return id;
};

export default useUserId;
import useUserId from "./UseUserId";
// ...rest of the necessary imports

const fetchData = async (userId) = > { // code to fetch data from GraphQl}

const useData = () => {
    const [data, setData] = useState();
    
    useEffect(() => { 
        const userId = useUser();
        fetchData(userId).then( // the rest of the code to set the state variable data.)
    },[])

    return data
   }

When I try to do this I get an error telling me

*Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:

  1. You might have mismatching versions of React and the renderer (such as React DOM)
  2. You might be breaking the Rules of Hooks
  3. You might have more than one copy of React in the same app See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.*

I think the problem is that I am calling the Hook useUser inside of the use effect, but using it inside the function will cause the problem described here, and I can't use it outside the body of the fetchData since the useData itself is a hook, and it can be only used inside a functional component's or Hook's body. So I don't know how to find a way around this problem.


Solution

  • Correct, React hooks can only be called from React function components and other React hooks. The useEffect hook's callback isn't a React hook, it's a callback. According to the Rules of Hooks, don't call hooks inside loops, conditions, or nested functions.

    I suggest refactoring the useData hook to consume the userId as an argument, to be used in the dependency array of the useEffect.

    const fetchData = async (userId) => {
      // code to fetch data from GraphQl
    };
    
    const useData = (userId) => {
      const [data, setData] = useState();
        
      useEffect(() => { 
        fetchData(userId)
          .then((....) => {
            // the rest of the code to set the state variable data.
          });
      }, [userId]);
    
      return data;
    };
    

    Usage in Function component:

    const userId = useUser();
    const data = useData(userId);
    

    If this is something that is commonly paired, abstract into a single hook:

    const useGetUserData = () => {
      const userId = useUser();
      const data = useData(userId);
      return data;
    };
    

    ...

    const data = useGetUserData();
    

    Though you should probably just implement as a single hook as follows:

    const useGetUserData = () => {
      const [data, setData] = useState();
    
      useEffect(() => {
        getUserInfo()
          .then(fetchData) // shortened (userId) => fetchData(userId)
          .then((....) => {
            // the rest of the code to set the state variable data.
            setData(....);
          });
      }, []);
    
      return data;
    };