I am trying to set the results of RTK query to the local state so that I can change the list based on user actions. See my full code below: but the relevant code is these two statements:
const { data: notifications = [], isLoading, isError } = useGetUserNotificationsQuery();
const [userNotificationRead] = useUserNotificationReadMutation();
const [notificationList, setNotificationList] = useState([]);
and
useEffect(() => {
setNotificationList(notifications);
}, [notifications]);
warning I am seeing is this: Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render. at NotificationList
and here is the full code
**import React, { useState, useEffect } from 'react';
import {
Avatar,
Paper,
Typography,
IconButton,
Menu,
MenuItem,
CircularProgress,
Box,
} from '@mui/material';
import MoreVertIcon from '@mui/icons-material/MoreVert';
import VolumeOffIcon from '@mui/icons-material/VolumeOff';
import NotificationsOffIcon from '@mui/icons-material/NotificationsOff';
import DeleteIcon from '@mui/icons-material/Delete';
import { Link, useNavigate } from 'react-router-dom';
import { useGetUserNotificationsQuery } from '../../redux/thunks/users/usernotifications';
import { useUserNotificationReadMutation } from '../../redux/thunks/users/usernotificationsmarkread'
const NotificationList = () => {
const [anchorEl, setAnchorEl] = useState(null);
const [selectedNotification, setSelectedNotification] = useState(null);
const { data: notifications = [], isLoading, isError } = useGetUserNotificationsQuery();
const [userNotificationRead] = useUserNotificationReadMutation();
const [notificationList, setNotificationList] = useState([]);
useEffect(() => {
setNotificationList(notifications);
}, [notifications]);
const handleMenuOpen = (event, notification) => {
setAnchorEl(event.currentTarget);
setSelectedNotification(notification);
};
const handleMenuClose = () => {
setAnchorEl(null);
setSelectedNotification(null);
};
const handleDeleteNotification = () => {
// Implement your logic to delete the selected notification here
console.log(`Deleted notification: ${selectedNotification.notice}`);
const updatedNotifications = notificationList.filter(
(notification) => notification._id !== selectedNotification._id
);
setNotificationList(updatedNotifications);
handleMenuClose();
};
const handleTurnOffNotifications = () => {
// Implement your logic to turn off notifications of this type here
console.log(`Turned off notifications from ${selectedNotification.senderDisplayName}`);
handleMenuClose();
};
const handleMuteNotifications = () => {
// Implement your logic to mute notifications from this sender here
console.log(`Muted notifications from ${selectedNotification.senderDisplayName}`);
handleMenuClose();
};
if (isLoading ) {
// Show a loading spinner if either user data or activities data is loading
return (
<Box display="flex" justifyContent="center" alignItems="center" minHeight="200px">
<CircularProgress color="primary" />
</Box>
)
}
console.log('in notifications: notificationList', notificationList)
console.log('in notifications: notifications', notifications)
if(!notificationList.length) {
return (
<Paper variant="outlined" justifyContent="center" sx={{
alignItems: 'center',
justifyContent: 'space-between',
padding: '8px',
marginBottom: '8px',
}}>
{/* Content for if notification is empty*/}
<Typography variant="h6" gutterBottom justifyContent="center" style={{ cursor: 'pointer' }}>
No notifications in your list
</Typography>
<Link
to={'/mynetwork'}
style={{ textDecoration: 'none', color: 'inherit' }}
>
<Typography variant="subtitle2" color="textSecondary">
Build your network
</Typography>
</Link>
</Paper>
)
}
return (
<div>
{notificationList.map((notification) => (
<Paper
key={notification._id}
variant="outlined"
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '8px',
marginBottom: '8px',
}}
>
{/* Left Column: User Photo */}
<Avatar
src={notification.senderPhoto}
alt={notification.senderDisplayName}
/>
{/* Middle Column: Notification */}
<Typography sx={{ flex: 1 }}>{notification.notice}</Typography>
{/* Right Column: Options */}
<IconButton
aria-label="Options"
aria-controls="notification-menu"
aria-haspopup="true"
onClick={(e) => handleMenuOpen(e, notification)}
>
<MoreVertIcon />
</IconButton>
</Paper>
))}
{/* Options Menu */}
<Menu
id="notification-menu"
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={handleMenuClose}
>
<MenuItem onClick={handleDeleteNotification}>
<DeleteIcon /> Delete
</MenuItem>
<MenuItem onClick={handleTurnOffNotifications}>
<NotificationsOffIcon /> Turn Off Notifications
</MenuItem>
<MenuItem onClick={handleMuteNotifications}>
<VolumeOffIcon /> Mute Notifications from {selectedNotification?.senderDisplayName}
</MenuItem>
</Menu>
</div>
);
};
export default NotificationList;**
notifications
appears to be an array. The issue here is that it seems the array is redeclared each time the component renders even though the contents may not have changed. You really only want to update the localnotificationList
state when thenotifications
array content changes. Tricks like using the array length also don't work in all cases, e.g. when the content changes but the arrays are the same length. An "acceptable hack" is to JSON stringify the array as a dependency.Example:
You might need to disable any
react-hooks
linting rules for this line if it complains about missing dependencies.An alternative might be to cache the "previous" version of
notifications
in a React ref and check if the array contents have changed. Again, JSON stringifying the array is a "quick hack", otherwise you could implement a true "deep equals" utility function for arrays.