Calling Hooks multiple times in React

In this post, I'll share how I ended up calling a Hook multiple times inside a component.

I will take the two approaches I followed (the first one is wrong but intuitive, the second one is correct but not evident).

First approach

Let's suppose that we create a Hook that counts the number of posts of a user. Let's call this Hook usePosts.js:

jsx
import React, {useState, useEffect} from 'react';
import getNumberOfPosts             from '../Functions/getNumberOfPosts';

const usePosts = (uid) => {

    const [posts, setPosts] = useState(0);
    
    useEffect( async () => {
    
        // Returns the number of posts for a user with uid
        let total = await getNumberOfPosts(uid);
        
        // Setting the total of posts
        setPosts(total);
        
    }, []);
    
    return posts;
}

export default usePosts;

Days later, let's suppose that we create Ranking.js to know the number of posts for each user. The component does as follows:

  1. It gets an array of uids.
  2. It gets the number of posts for each uid.
jsx
import React, {useState, useEffect} from 'react';
import getUsers                     from '../Functions/getUsers';
import usePosts                     from '../Hooks/usePosts';

const Ranking = () => {

    const [users, setUsers] = useState([]);
    
    useEffect( async () => {
    
        // Returns a user id array
        let array = await getUsers();
        
        // Setting the array of users
        setUsers(array);
        
    
    }, []);
    
    // 🔴 At this point we want to know the posts of each user
    // 🔴 So, first choice would be use map function, but...
    // 🔴 This solution is wrong
    // 🔴 Because you can't call a Hook inside a loop or a function
    const usersPosts = users.map( uid => usePosts(uid) );
    
}

export default Ranking;

Second approach

Instead of one argument, useHooks.js will get several. We can use the spread operator ... to accomplish this. So, let's modify the Hook:

jsx
import React, {useState, useEffect} from 'react';
import getNumberOfPosts             from '../Functions/getNumberOfPosts';

const usePosts = async (...uids) => {

    const [posts, setPosts] = useState([]);
    
    useEffect( () => {
    
        // Looping through uids parameter
        let total = await Promises.all( uids.map( uid => getNumberOfPosts(uid)) );
        
        // Setting the total of posts
        setPosts(total);
        
    }, []);
    
    return posts;

}

export default usePosts;

Now the Hook returns an array of posts depending on the uids parameter.

If we want to rank the users:

jsx
import React, {useState, useEffect} from 'react';
import getUsers                     from '../Functions/getUsers';
import usePosts                     from  '../Hooks/usePosts';

const Ranking = () => {

    const [uids, setUids] = useState([]);
    
    useEffect( async () => {
    
        // Listener that returns a user id array
        let array = await getUsers();
        
        // Setting the array of users
        setUids(array);
        
    }, []);
    
    // ✅ At this point, we want to know the posts for each user
    // ✅ Now we can pass an array of users as parameter
    // ✅ And we will get an array in return
    const usersPosts = usePosts(...uids);
    
    return usersPosts;
    
}

export default Ranking;

Conclusion

Rather than calling the Hook multiple times, it was better to pass an array of arguments to the Hook and to get an array of results.

Hi, I'm Erik, an engineer from Barcelona. If you like the post or have any comments, say hi.