skip to Main Content

I have a list of posts that I am mapping through and want to get the associated author data and avatar photo url, for each post. I have functions that make api calls and return this information, when called on each post. Then the username, full_name & avatar_url are added to the post, so that when rendering the data in the component, all of the data is available.

See the custom hook:

// Custom hook to fetch the user & avatar for each post and combine them
function usePostsWithUserDetails(posts) {
  const [combinedPosts, setCombinedPosts] = useState([]);

  useEffect(() => {
    // Make async call for session & avatar_url that match the post
    const fetchData = async () => {
      const combineData = posts.map(async (post) => {
        const [user, avatar] = await Promise.all([
          getSessionProfile(post.user_id),
          fetchProfileAvatar(post.user_id),
        ]);
        // Add the needed data to the post
        post.username = user.username;
        post.full_name = user.full_name;
        post.avatar_url = avatar;
      });

      setCombinedPosts(combineData);
    };

    fetchData();
  }, [])

  return combinedPosts;
}

So my problem is that when I go to add the data to the post (ie: post.username = user.username), that data may not have been returned from the async function. I am fairly new to React and still haven’t gotten the hang of how to return the promise when it’s needed.

I believe you would normally want to add the data, after it’s returned from the useEffect. This issue being that this needs to happen for each post. The only other solution I can think of is to have the needed data added to the table when the post is created but at some point I am going to need to query other tables and combine data.

Any direction is much appreciated. 🙂

2

Answers


  1. Chosen as BEST ANSWER

    Update One: (using some hints from Dominic to try and fit it all in one function)

    // Custom hook to fetch the user & avatar for each post and combine them
    function usePostsWithUserDetails(posts) {
      const [combinedPosts, setCombinedPosts] = useState([]);
    
      useEffect(() => {
        // Make async call for session & avatar_url that match the post
        const fetchData = async () => {
          const combineData = await Promise.all(posts.map(async (post) => {
            const [user, avatar] = await Promise.all([
              getSessionProfile(post.user_id),
              fetchProfileAvatar(post.user_id),
            ]);
            
            post.username = user[0].username;
            post.full_name = user[0].full_name;
            post.avatar_url = avatar;
    
            return post
          }));
    
          setCombinedPosts(combineData);
        };
    
        fetchData();
      }, [posts])
    
      return combinedPosts;
    }
    

    Functions from apiService to fetch the profile and avatar from Supabase:

    export async function getSessionProfile(userId) {
      let { data: profiles, error } = await supabase
      .from('profiles')
      .select('*')
      .eq('id', userId);
    
      return profiles;
    }
    

    Fetch Avatar:

    export async function fetchProfileAvatar (userId) {
      const avatarList = await listFilesInBucket('avatars', '');
    
      const avatarExists = avatarList.filter((avatar) => {
        return avatar.name.includes(userId);
      });
    
      const avatarURL = avatarExists[0].name ? await getImageURL('avatars', avatarExists[0].name).then(data => data.publicUrl) : '/static/default-avatar.png';
    
      return avatarURL;
    }
    

  2. You need to wrap your map in a Promise.all. Also you should return the modified post in the map loop.

    function getSessionProfile() {
      return new Promise((resolve) => {
        resolve({
          username: 'bob',
          full_name: 'Billy Bob'
        })
      })
    }
    
    function fetchProfileAvatar() {
      return new Promise((resolve) => {
        resolve('https://avatar.com')
      })
    }
    
    const posts = [{ id: 1 }]
    
    const combineData = () => Promise.all(posts.map(async (post) => {
      const [user, avatar] = await Promise.all([
        getSessionProfile(post.user_id),
        fetchProfileAvatar(post.user_id),
      ]);
      
      return {
        ...post,
        // Add the needed data to the post
        username: user.username,
        full_name: user.full_name,
        avatar_url: avatar,  
      }
    }));
    
    (async () => {
      const combinedData = await combineData()
      console.log(combinedData)
    })()
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search