알림닫기 오류 수정
This commit is contained in:
@@ -149,6 +149,8 @@ export const TradeNotificationProvider: React.FC<ProviderProps> = ({
|
||||
const toastNotificationsRef = useRef(toastNotifications);
|
||||
toastNotificationsRef.current = toastNotifications;
|
||||
const [allNotifications, setAllNotifications] = useState<TradeNotificationItem[]>([]);
|
||||
const allNotificationsRef = useRef(allNotifications);
|
||||
allNotificationsRef.current = allNotifications;
|
||||
const [detailSignal, setDetailSignal] = useState<TradeSignalInfo | null>(null);
|
||||
const [detailCentered, setDetailCentered] = useState(false);
|
||||
|
||||
@@ -161,11 +163,14 @@ export const TradeNotificationProvider: React.FC<ProviderProps> = ({
|
||||
try {
|
||||
const dtos = await loadTradeSignals();
|
||||
/**
|
||||
* 항상 DB 캐시의 최신 readIds 사용.
|
||||
* rawServerList=true 시 빈 Set을 쓰던 기존 코드는 새로고침 후 모든 알림이
|
||||
* 미읽음으로 복원되는 버그를 유발했으므로 제거.
|
||||
* readIds 의 최신 상태를 가져올 때:
|
||||
* - readIdsRef.current : dismissAllToasts / markAllAsRead 직후 즉시 반영되는 인메모리 값
|
||||
* - loadReadIds() : DB 캐시 기반 값 (약간의 지연 가능)
|
||||
* 두 Set 의 합집합(union)을 사용하면 어느 쪽 타이밍 경쟁에서도 읽음 상태가 보존된다.
|
||||
*/
|
||||
const read = loadReadIds();
|
||||
const cacheRead = loadReadIds();
|
||||
const memRead = readIdsRef.current;
|
||||
const read = new Set([...cacheRead, ...memRead]);
|
||||
const hidden = opts?.rawServerList ? new Set<string>() : loadHiddenIds();
|
||||
const items = dtos
|
||||
.map(d => dtoToItem(d, read))
|
||||
@@ -338,35 +343,39 @@ export const TradeNotificationProvider: React.FC<ProviderProps> = ({
|
||||
|
||||
const dismissAllToasts = useCallback(() => {
|
||||
/**
|
||||
* 전체 닫기 = 토스트 + allNotifications 의 모든 항목을 즉시 읽음 처리.
|
||||
* patchUiPreferences immediate=true 로 디바운스 없이 즉시 DB에 반영해
|
||||
* 이후 폴링·STOMP 재수신 시에도 readIds 가 항상 최신 상태를 유지한다.
|
||||
* 전체 닫기 — 현재 표시된 모든 알림을 읽음 처리하고 즉시 DB 저장.
|
||||
*
|
||||
* 핵심 원칙:
|
||||
* 1. 사이드 이펙트(readIdsRef 갱신, DB 저장)는 setState 업데이터 밖에서 실행.
|
||||
* 2. allNotificationsRef 로 현재 목록을 동기적으로 읽어 nextRead 를 구성.
|
||||
* 3. immediate=true 로 400ms 디바운스 없이 즉시 저장해 이후 폴링과의
|
||||
* 경쟁 조건(readIds 가 아직 DB 에 없는 상태)을 제거.
|
||||
*/
|
||||
const nextRead = new Set(readIdsRef.current);
|
||||
toastNotificationsRef.current.forEach(n => nextRead.add(n.id));
|
||||
// allNotifications 는 state 업데이트 전이므로 ref가 아닌 최신 snapshot이 필요.
|
||||
// setAllNotifications 콜백 안에서 nextRead를 채운 뒤 즉시 저장한다.
|
||||
setAllNotifications(prev => {
|
||||
prev.forEach(n => nextRead.add(n.id));
|
||||
readIdsRef.current = nextRead;
|
||||
setReadIds(nextRead);
|
||||
// immediate=true 로 400ms 디바운스 없이 즉시 저장
|
||||
patchUiPreferences({ tradeNotifications: { readIds: [...nextRead] } }, true);
|
||||
return prev.map(n => ({ ...n, isRead: true }));
|
||||
});
|
||||
allNotificationsRef.current.forEach(n => nextRead.add(n.id));
|
||||
|
||||
// 1) 인메모리 ref 즉시 갱신 — addNotification / refreshHistory 가 즉시 참조
|
||||
readIdsRef.current = nextRead;
|
||||
|
||||
// 2) React state 갱신 (렌더링 예약)
|
||||
setReadIds(nextRead);
|
||||
setAllNotifications(prev => prev.map(n => ({ ...n, isRead: true })));
|
||||
setToastNotifications([]);
|
||||
|
||||
// 3) 캐시 갱신 + 즉시 DB 저장 (immediate=true)
|
||||
saveReadIds(nextRead, true);
|
||||
}, []);
|
||||
|
||||
const markAllAsRead = useCallback(() => {
|
||||
setAllNotifications(prev => {
|
||||
const nextRead = new Set(loadReadIds());
|
||||
prev.forEach(n => nextRead.add(n.id));
|
||||
saveReadIds(nextRead);
|
||||
readIdsRef.current = nextRead;
|
||||
setReadIds(nextRead);
|
||||
return prev.map(n => ({ ...n, isRead: true }));
|
||||
});
|
||||
const nextRead = new Set(readIdsRef.current);
|
||||
allNotificationsRef.current.forEach(n => nextRead.add(n.id));
|
||||
|
||||
readIdsRef.current = nextRead;
|
||||
setReadIds(nextRead);
|
||||
setAllNotifications(prev => prev.map(n => ({ ...n, isRead: true })));
|
||||
setToastNotifications([]);
|
||||
saveReadIds(nextRead, true);
|
||||
}, []);
|
||||
|
||||
const purgeNotifications = useCallback((ids: string[]) => {
|
||||
|
||||
Reference in New Issue
Block a user