skip to Main Content

I built a MERN project, where the component FrindListWidget is being called on 2 conditions. 

  1. If it’s the user’s own friendlist, the widget will be rendered as well as a state in Redux user.friends will also update.  
  2. If it is another user, only the widget will be rendered. No Redux state will update. 

Each Friend also contains an <IconButton> that will add or remove Friend
Apparently, everything was working pretty well until I checked the network tab in the Chrome developer tool. I detected that friends are being called for infinite times. To make it clear, I wrote console.log("friends", friends);. And yes it’s being logged for infinite times.
I am sharing the code below:

FriendListWidget.jsx

//other mui imports
import { WidgetWrapper } from "../../../components/StyledComponent/WidgetWrapper";
import { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { setFriends as setUsersFriends } from "../../../Redux/Slices/authSlice";
import { userRequest } from "../../../requestMethod";

import Friend from "../../../components/Friend";

const FriendListWidget = ({ userId }) => {
  const dispatch = useDispatch();
  const { palette } = useTheme();
  const [friends, setFriends] = useState([]);
  const user = useSelector((state) => state.user);

  const getFriends = async () => {
    const res = await userRequest.get(`/users/${userId}/friends`);
    const data = await res.data;
    if (user._id === userId) {
      dispatch(setUsersFriends({ friends: data }));
      setFriends(data);
    } else {
      setFriends(data);
    }
  };

  useEffect(() => {
    getFriends();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [user, userId]);

  console.log("friends", friends); //This is being logged for infinite times

  return (
    <WidgetWrapper>
      <Typography
        color={palette.neutral.dark}
        variant="h5"
        fontWeight="500"
        sx={{ mb: "1.5rem" }}
      >
        Friend List
      </Typography>
      <Box display="flex" flexDirection="column" gap="1.5rem">
        {friends.map((friend) => (
          <Friend
            key={friend._id}
            friendId={friend._id}
            name={`${friend.firstName} ${friend.lastName}`}
            subtitle={friend.occupation}
            userPicturePath={friend.picturePath}
          />
        ))}
      </Box>
    </WidgetWrapper>
  );
};

export default FriendListWidget;

Friend.jsx

//other mui imports
import { useDispatch, useSelector } from "react-redux";
import { useNavigate } from "react-router-dom";
import { userRequest } from "../../requestMethod";
import { setFriends } from "../../Redux/Slices/authSlice";
import { FlexBetween } from "../StyledComponent/FlexBetween";
import UserImage from "../StyledComponent/UserImage";

const Friend = ({ friendId, name, location, userPicturePath }) => {
  const dispatch = useDispatch();
  const navigate = useNavigate();
  const { _id } = useSelector((state) => state.user);
  const friends = useSelector((state) => state.user.friends);

  const { palette } = useTheme();
  const primaryLight = palette.primary.light;
  const primaryDark = palette.primary.dark;
  const main = palette.neutral.main;
  const medium = palette.neutral.medium;

  const isFriend = friends.find((friend) => friend._id === friendId);
  const isUser = friendId === _id;

  const patchFriend = async () => {
    const res = await userRequest.patch(`/users/${_id}/${friendId}`);
    const data = await res.data;
    dispatch(setFriends({ friends: data }));
  };

  return (
    <FlexBetween>
      <FlexBetween gap="1rem">
        <UserImage image={userPicturePath} size="55px" />
        <Box
          onClick={() => {
            navigate(`/profile/${friendId}`);
            navigate(0);
          }}
        >
          <Typography
            color={main}
            variant="h5"
            fontWeight="500"
            sx={{
              "&:hover": {
                color: palette.primary.light,
                cursor: "pointer",
              },
            }}
          >
            {name}
          </Typography>
          <Typography color={medium} fontSize="0.75rem">
            {location}
          </Typography>
        </Box>
      </FlexBetween>
      {!isUser && (
        <IconButton
          onClick={() => patchFriend()}
          sx={{ backgroundColor: primaryLight, p: "0.6rem" }}
        >
          {isFriend ? (
            <PersonRemoveOutlined sx={{ color: primaryDark }} />
          ) : (
            <PersonAddOutlined sx={{ color: primaryDark }} />
          )}
        </IconButton>
      )}
    </FlexBetween>
  );
};

export default Friend;

userRequest is just an axios method:

export const userRequest = axios.create({
  baseURL: BASE_URL,
  headers: { token: `Bearer ${token}` },
});

I tried removing thed dependencies in the useEffect hook:

useEffect(() => {
    getFriends();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

But, it only renders once. It doesn’t show the updates through <IconButton> in the runtime. I’ve to reload the window to see the update.

2

Answers


  1. Chosen as BEST ANSWER

    I guess I solved the problem. Friend component is called inside other components too such as PostsWidget. I tried @Mohammad A. Souri's s solution. But it only works for FreindListWidget. Not for the other components. So, I created a loading state in redux to access it globally. And then I put it as the dependency in useEffect hooks. So the code looks like below:

    authSlice.js (redux slice)

    import { createSlice } from "@reduxjs/toolkit";
    
    const initialState = {
      mode: "light",
      user: null,
      token: null,
      posts: [],
      loading: false,
    };
    
    export const authSlice = createSlice({
      name: "auth",
      initialState,
      reducers: {
    //other reducers
        setLoading: (state, action) => {
          state.loading = action.payload;
        },
      },
    });
    
    export const { setLoading } = authSlice.actions;
    export default authSlice.reducer;
    

    Friend.jsx

    //rest of the code
    import { setLoading } from "../../Redux/Slices/authSlice";
    
    const Friend = ({ friendId, name, location, userPicturePath }) => {
    //rest of the code
      const patchFriend = async () => {
        const res = await userRequest.patch(`/users/${_id}/${friendId}`);
        const data = await res.data;
        dispatch(setFriends({ friends: data }));
        dispatch(setLoading(true));
      };
    
    //rest of the code
    }
    
    

    FriendListWidget.jsx

    //rest of the code
    import { setLoading } from "../../../Redux/Slices/authSlice";
    const FriendListWidget = ({ userId }) => {
      const loading = useSelector((state) => state.loading);
      const getFriends = async () => {
        try {
          const res = await userRequest.get(`/users/${userId}/friends`);
          const data = await res.data;
          setFriends(data);
          dispatch(setLoading(false)); // Set loading to false when API call succeeds
        } catch (error) {
          console.error("Error fetching friends:", error);
          dispatch(setLoading(false)); // Set loading to false when API call fails
        }
      };
      useEffect(() => {
        getFriends();
      }, [userId, loading, dispatch]);
    
      useEffect(() => {
        if (user._id === userId) {
          dispatch(setUsersFriends({ friends: friends }));
        }
      }, [dispatch, friends, user._id, userId]);
    
      console.log("loading", loading);
      console.log("friends", friends);
    //rest of the code
    }
    

    Note: Anyone not using redux can try Context api to declare the global state.


  2. I assume you are calling getFriends() which updates list of users you injected into your useEffect, therefore, making useEffect to update itself again for infinite times, try to make your useEffect to depend on another value.

      useEffect(() => {
        getFriends();
        // eslint-disable-next-line react-hooks/exhaustive-deps
      }, [userId]);
    

    for such scnearios I personally use react-query having enabled property to let me decide when to repeat my API call again, here however, your logic has strict issues which I can’t get to understand it, if you could make a minimal reproducible example of your issue using platforms like CodeSandBox, we can help you better.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search