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