Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 2024-03-23 - Memoize Derived State to Prevent Extra Renders
**Learning:** Using `useEffect` to map or reduce props into local `useState` causes an unnecessary double-render cycle in React. In `NotificationMenu` and `NotificationTabPanel`, parsing `notificationsData` into `today` and `older` buckets on mount or update triggered this pattern, unnecessarily blocking paint and wasting CPU cycles.
**Action:** Replace `useState` + `useEffect` chains for derived state with `useMemo`. This allows the derived calculation to run synchronously during the render phase only when dependencies (`notificationsData`) change, saving an entire render pass.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useState } from 'react';
import { useMemo } from 'react';
import TabPanel from '@mui/lab/TabPanel';
import { Button, Divider, dividerClasses, listItemClasses } from '@mui/material';
import dayjs from 'dayjs';
Expand All @@ -11,13 +11,14 @@ interface NotificationTabPanelProps {
}

const NotificationTabPanel = ({ value, notificationsData }: NotificationTabPanelProps) => {
const [notifications, setNotifications] = useState<DatewiseNotification>({
today: [],
older: [],
});

useEffect(() => {
const datewiseNotification = notificationsData.reduce(
/**
* ⚑ Bolt Performance Optimization:
* Replaced `useEffect` + `useState` derived state calculation with `useMemo`.
* This removes an unnecessary extra render cycle whenever `notificationsData` changes,
* avoiding layout shifts and reducing CPU overhead during component mounting/updating.
*/
const notifications = useMemo(() => {
return notificationsData.reduce(
(acc: DatewiseNotification, val) => {
if (dayjs().diff(dayjs(val.createdAt), 'days') === 0) {
acc.today.push(val);
Expand All @@ -31,8 +32,6 @@ const NotificationTabPanel = ({ value, notificationsData }: NotificationTabPanel
older: [],
},
);

setNotifications(datewiseNotification);
}, [notificationsData]);

return (
Expand Down
18 changes: 9 additions & 9 deletions client/src/layouts/main-layout/common/NotificationMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { useEffect, useState } from 'react';
import { useMemo, useState } from 'react';
import { badgeClasses, Box, Button, Link, paperClasses, Popover, Stack } from '@mui/material';
import { notifications as notificationsData } from 'data/notifications';
import dayjs from 'dayjs';
Expand All @@ -17,10 +17,6 @@ interface NotificationMenuProps {
}

const NotificationMenu = ({ type = 'default' }: NotificationMenuProps) => {
const [notifications, setNotifications] = useState<DatewiseNotification>({
today: [],
older: [],
});
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);

const {
Expand All @@ -35,8 +31,14 @@ const NotificationMenu = ({ type = 'default' }: NotificationMenuProps) => {
setAnchorEl(null);
};

useEffect(() => {
const datewiseNotification = notificationsData.reduce(
/**
* ⚑ Bolt Performance Optimization:
* Replaced `useEffect` + `useState` derived state calculation with `useMemo`.
* This removes an unnecessary extra render cycle whenever `notificationsData` changes,
* avoiding layout shifts and reducing CPU overhead during component mounting/updating.
*/
const notifications = useMemo(() => {
return notificationsData.reduce(
(acc: DatewiseNotification, val) => {
if (dayjs().diff(dayjs(val.createdAt), 'days') === 0) {
acc.today.push(val);
Expand All @@ -50,8 +52,6 @@ const NotificationMenu = ({ type = 'default' }: NotificationMenuProps) => {
older: [],
},
);

setNotifications(datewiseNotification);
}, [notificationsData]);

return (
Expand Down