I built a MERN project, where the component FrindListWidget
is being called on 2 conditions.
- 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. - 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
I guess I solved the problem.
Friend
component is called inside other components too such asPostsWidget
. I tried @Mohammad A. Souri's s solution. But it only works forFreindListWidget
. Not for the other components. So, I created aloading
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)
Friend.jsx
FriendListWidget.jsx
Note: Anyone not using redux can try Context api to declare the global state.
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.
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.