0

I am making MERN social media app.

I want to show all the friends of the current user in a list in SideBar.jsx .

Home.jsx (parent of Sidebar.jsx)

import React, { Component } from "react";
import { Person } from "@material-ui/icons";
import Topbar from "../../components/topbar/Topbar";
import Sidebar from "../../components/sidebar/Sidebar";
import Feed from "../../components/feed/Feed";
import Rightbar from "../../components/rightbar/Rightbar";
import "./home.css";
export default function Home() {
  return (
    <>
      <Topbar />
      <div className="homeContainer">
        <Sidebar />
        <Feed />
        <Rightbar />
      </div>
    </>
  );
}

SideBar.jsx

import "./sidebar.css";
import React, { Component, useContext, useState } from "react";
...
import { axiosInstance } from "../../config";

export default function Sidebar() {
  const { user } = useContext(AuthContext);
  const [followings, setFollowings] = useState([]);
  const followingsList = user.followings;
  useEffect(() => {
    const fetchFollowings = async () => {
      followingsList.map(async (id) => {
        try {
          const theUser = await axiosInstance.get(`/users?userId=${id}`);
          if (followings.includes(theUser.data)) {
          } else {
            setFollowings((prev) => [...prev, theUser.data]);
          }
        } catch (error) {
          console.log(error);
        }

      });
    };
    fetchFollowings();
  }, [user]);
  return (
    <div className="sidebar">
  .....
        <ul className="sidebarFriendList">
          {followings.map((u) => (
            <CloseFriend key={u._id} user={u} />
          ))}
        </ul>
      </div>
    </div>
  );
}


For example, in this case, in the state "followings", there are 2 user objects.

So, the line

 followings.map((u) => (...

should only show 2 entries.

However, the result is below.

enter image description here

As you can see, it is showing each friend twice. I tired to check if a user object already exists in followings by doing

 if (followings.includes(theUser.data)) {
          } else {
            setFollowings((prev) => [...prev, theUser.data]);
          }

But this is not working.

How can I make sure that it only shows each user once?

I want it to be like this enter image description here

Any help would be greatly appreciated. thank you

Hayato
  • 61
  • 1
  • 6
  • Add some debugging console logs, one for `u` at the rendering, one in the state setting call. Even try saving the new state array to a variable in `setFollowings` and log it. – Brother58697 Aug 01 '22 at 18:37
  • When you have 3 followings every follower will be displayed three times right? – PRSHL Aug 01 '22 at 18:37

2 Answers2

1

When you are using the .map function with async/await Promise.all usually always does the trick. Instead of pushing the state on every iteration you collect the followers list and set the state when all your fetching is done. I did not test it yet, but I hope it works.

const followingsList = user.followings;

useEffect(() => {
    const fetchFollowings = async () => {
      const list = await Promise.all(followingsList.map(async (id) => (
        await axios.get('/user?userId=' + id);
      )));
      
      setFollowers(list);
    };
    fetchFollowings();
  }, [user]);

Note: let me know if it works, if not I'll do a little sandbox on my own

PRSHL
  • 1,359
  • 1
  • 11
  • 30
1

This is happening because it seems that your useEffect method is being fired two times (probably because you are using React.StrictMode) and you are setting the state inside the .map method (that is not good because you trigger a new render each time you call the setState).

What I would recommend you to do, is to remove the setState from the .map method and just set the new state after you format your data. So it would be something like this:

const newFollowings = followingsList.map(async (id) => {
  try {
    const theUser = await axiosInstance.get(`/users?userId=${id}`);
    return theUser.data;
  } catch (error) {
    console.log(error);
  }
});

setFollowings(newFollowings);

Probably you would have to add a filtering to the array in case there are some errors (because on errors the mapped value would be undefined):

.filter(data => data);
DrunkOldDog
  • 718
  • 5
  • 12
  • Thank you for your clear explanation. I'm not really familiar with React.StrictMode, but the takeAway is that I should not setState in .map method, since it'll trigger re-rendering while it's doing the map method, that is why im seeing this behavior of map method, is it pretty correct? – Hayato Aug 01 '22 at 20:53
  • Well, in part. You were seeing the behavior of duplicate values because of the `(prev) => [...prev, theUser.data]` declaration. There, you were retrieving the previous state values and adding new values (when triggering the `useEffect` twice, you were adding that extra two values to your state). With the current answer approach, you are setting a completely new state, so if the `useEffect` triggers twice we are just overriding the previous values. For a reference of `React.StrictMode` (not sure if this is your case) you can check this answer: https://stackoverflow.com/a/61897567/12222861 – DrunkOldDog Aug 01 '22 at 23:31