Files
pn-new-crm/src/hooks/use-notifications.ts

47 lines
1.4 KiB
TypeScript
Raw Normal View History

'use client';
fix(compiler): React Compiler safety triage — 5 categories cleared Cleared 4 rule buckets (37 violations, including 5 real bugs) and silenced 1 informational bucket from the Next 16 / react-hooks v7 upgrade. Cleared rules promoted from `warn` back to `error` so new regressions block CI. Real bug fixes: - `interest-contact-log-tab.tsx`: `useMemo` used for side effects (5 setState calls inside a memo body); converted to `useEffect`. - `PieChart.tsx`: cumulative `let angle` mutation in a render-phase `map`; converted to `reduce` so the slice array is built without re-assignment. - `documents-hub.tsx`: `useMemo(() => ({ count: 0 }))` used as a mutable drag counter; converted to `useRef`. - `notes-list.tsx`: `Date.now()` read during render for note-edit countdown (impure) → pinned to a `now` state ticked every 30s. - `onboarding-checklist.tsx` / `user-profile.tsx` / `user-settings.tsx`: `useEffect(() => void load(), [])` with the `load` function declared AFTER the effect — relied on hoisting, trips Compiler's "access before declared" rule. Declared inside the effect. Pattern fixes (intentional cache-via-ref → state or layout-effect): - 6 `ref.current = x` writes during render moved into layout effects (`use-realtime-invalidation`, `settings-form-card`, `inbox`). - 3 `ref.current` reads during render (search totals cache, scanner file ref) rewritten to backed-by-state. - `use-is-mobile.ts` rewritten on `useSyncExternalStore` to avoid the SSR-then-rehydrate setState dance. - `use-notifications.ts` rewritten to write socket pushes directly into the React Query cache via `setQueryData`, removing a local state mirror. Rule config (`eslint.config.mjs`): - `react-hooks/purity` → error (was warn, cleared) - `react-hooks/set-state-in-render` → error (was warn, cleared) - `react-hooks/immutability` → error (was warn, cleared) - `react-hooks/refs` → error (was warn, cleared) - `react-hooks/incompatible-library` → off (informational only) - `react-hooks/set-state-in-effect` → warn (51 remaining, all the useEffect→fetch→setState data-fetch pattern; migration to useQuery tracked in BACKLOG) Verified: tsc clean, eslint 0 errors / 69 warnings (down from 105), vitest 1315/1315, next build green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 23:14:16 +02:00
import { useEffect } from 'react';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { useSocket } from '@/providers/socket-provider';
import { apiFetch } from '@/lib/api/client';
fix(compiler): React Compiler safety triage — 5 categories cleared Cleared 4 rule buckets (37 violations, including 5 real bugs) and silenced 1 informational bucket from the Next 16 / react-hooks v7 upgrade. Cleared rules promoted from `warn` back to `error` so new regressions block CI. Real bug fixes: - `interest-contact-log-tab.tsx`: `useMemo` used for side effects (5 setState calls inside a memo body); converted to `useEffect`. - `PieChart.tsx`: cumulative `let angle` mutation in a render-phase `map`; converted to `reduce` so the slice array is built without re-assignment. - `documents-hub.tsx`: `useMemo(() => ({ count: 0 }))` used as a mutable drag counter; converted to `useRef`. - `notes-list.tsx`: `Date.now()` read during render for note-edit countdown (impure) → pinned to a `now` state ticked every 30s. - `onboarding-checklist.tsx` / `user-profile.tsx` / `user-settings.tsx`: `useEffect(() => void load(), [])` with the `load` function declared AFTER the effect — relied on hoisting, trips Compiler's "access before declared" rule. Declared inside the effect. Pattern fixes (intentional cache-via-ref → state or layout-effect): - 6 `ref.current = x` writes during render moved into layout effects (`use-realtime-invalidation`, `settings-form-card`, `inbox`). - 3 `ref.current` reads during render (search totals cache, scanner file ref) rewritten to backed-by-state. - `use-is-mobile.ts` rewritten on `useSyncExternalStore` to avoid the SSR-then-rehydrate setState dance. - `use-notifications.ts` rewritten to write socket pushes directly into the React Query cache via `setQueryData`, removing a local state mirror. Rule config (`eslint.config.mjs`): - `react-hooks/purity` → error (was warn, cleared) - `react-hooks/set-state-in-render` → error (was warn, cleared) - `react-hooks/immutability` → error (was warn, cleared) - `react-hooks/refs` → error (was warn, cleared) - `react-hooks/incompatible-library` → off (informational only) - `react-hooks/set-state-in-effect` → warn (51 remaining, all the useEffect→fetch→setState data-fetch pattern; migration to useQuery tracked in BACKLOG) Verified: tsc clean, eslint 0 errors / 69 warnings (down from 105), vitest 1315/1315, next build green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 23:14:16 +02:00
const UNREAD_KEY = ['notifications', 'unread-count'] as const;
export function useNotifications() {
const socket = useSocket();
const queryClient = useQueryClient();
fix(compiler): React Compiler safety triage — 5 categories cleared Cleared 4 rule buckets (37 violations, including 5 real bugs) and silenced 1 informational bucket from the Next 16 / react-hooks v7 upgrade. Cleared rules promoted from `warn` back to `error` so new regressions block CI. Real bug fixes: - `interest-contact-log-tab.tsx`: `useMemo` used for side effects (5 setState calls inside a memo body); converted to `useEffect`. - `PieChart.tsx`: cumulative `let angle` mutation in a render-phase `map`; converted to `reduce` so the slice array is built without re-assignment. - `documents-hub.tsx`: `useMemo(() => ({ count: 0 }))` used as a mutable drag counter; converted to `useRef`. - `notes-list.tsx`: `Date.now()` read during render for note-edit countdown (impure) → pinned to a `now` state ticked every 30s. - `onboarding-checklist.tsx` / `user-profile.tsx` / `user-settings.tsx`: `useEffect(() => void load(), [])` with the `load` function declared AFTER the effect — relied on hoisting, trips Compiler's "access before declared" rule. Declared inside the effect. Pattern fixes (intentional cache-via-ref → state or layout-effect): - 6 `ref.current = x` writes during render moved into layout effects (`use-realtime-invalidation`, `settings-form-card`, `inbox`). - 3 `ref.current` reads during render (search totals cache, scanner file ref) rewritten to backed-by-state. - `use-is-mobile.ts` rewritten on `useSyncExternalStore` to avoid the SSR-then-rehydrate setState dance. - `use-notifications.ts` rewritten to write socket pushes directly into the React Query cache via `setQueryData`, removing a local state mirror. Rule config (`eslint.config.mjs`): - `react-hooks/purity` → error (was warn, cleared) - `react-hooks/set-state-in-render` → error (was warn, cleared) - `react-hooks/immutability` → error (was warn, cleared) - `react-hooks/refs` → error (was warn, cleared) - `react-hooks/incompatible-library` → off (informational only) - `react-hooks/set-state-in-effect` → warn (51 remaining, all the useEffect→fetch→setState data-fetch pattern; migration to useQuery tracked in BACKLOG) Verified: tsc clean, eslint 0 errors / 69 warnings (down from 105), vitest 1315/1315, next build green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 23:14:16 +02:00
// Single source of truth: React Query cache. Socket pushes write
// directly into the cache via setQueryData so we don't fight the
// query → local state → query refetch ordering that the previous
// useEffect-into-setState dance had.
const { data } = useQuery<{ count: number }>({
fix(compiler): React Compiler safety triage — 5 categories cleared Cleared 4 rule buckets (37 violations, including 5 real bugs) and silenced 1 informational bucket from the Next 16 / react-hooks v7 upgrade. Cleared rules promoted from `warn` back to `error` so new regressions block CI. Real bug fixes: - `interest-contact-log-tab.tsx`: `useMemo` used for side effects (5 setState calls inside a memo body); converted to `useEffect`. - `PieChart.tsx`: cumulative `let angle` mutation in a render-phase `map`; converted to `reduce` so the slice array is built without re-assignment. - `documents-hub.tsx`: `useMemo(() => ({ count: 0 }))` used as a mutable drag counter; converted to `useRef`. - `notes-list.tsx`: `Date.now()` read during render for note-edit countdown (impure) → pinned to a `now` state ticked every 30s. - `onboarding-checklist.tsx` / `user-profile.tsx` / `user-settings.tsx`: `useEffect(() => void load(), [])` with the `load` function declared AFTER the effect — relied on hoisting, trips Compiler's "access before declared" rule. Declared inside the effect. Pattern fixes (intentional cache-via-ref → state or layout-effect): - 6 `ref.current = x` writes during render moved into layout effects (`use-realtime-invalidation`, `settings-form-card`, `inbox`). - 3 `ref.current` reads during render (search totals cache, scanner file ref) rewritten to backed-by-state. - `use-is-mobile.ts` rewritten on `useSyncExternalStore` to avoid the SSR-then-rehydrate setState dance. - `use-notifications.ts` rewritten to write socket pushes directly into the React Query cache via `setQueryData`, removing a local state mirror. Rule config (`eslint.config.mjs`): - `react-hooks/purity` → error (was warn, cleared) - `react-hooks/set-state-in-render` → error (was warn, cleared) - `react-hooks/immutability` → error (was warn, cleared) - `react-hooks/refs` → error (was warn, cleared) - `react-hooks/incompatible-library` → off (informational only) - `react-hooks/set-state-in-effect` → warn (51 remaining, all the useEffect→fetch→setState data-fetch pattern; migration to useQuery tracked in BACKLOG) Verified: tsc clean, eslint 0 errors / 69 warnings (down from 105), vitest 1315/1315, next build green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 23:14:16 +02:00
queryKey: UNREAD_KEY,
queryFn: () => apiFetch('/api/v1/notifications/unread-count'),
staleTime: 30_000,
});
useEffect(() => {
if (!socket) return;
const handleNew = () => {
queryClient.invalidateQueries({ queryKey: ['notifications'] });
};
const handleCount = (payload: { count: number }) => {
fix(compiler): React Compiler safety triage — 5 categories cleared Cleared 4 rule buckets (37 violations, including 5 real bugs) and silenced 1 informational bucket from the Next 16 / react-hooks v7 upgrade. Cleared rules promoted from `warn` back to `error` so new regressions block CI. Real bug fixes: - `interest-contact-log-tab.tsx`: `useMemo` used for side effects (5 setState calls inside a memo body); converted to `useEffect`. - `PieChart.tsx`: cumulative `let angle` mutation in a render-phase `map`; converted to `reduce` so the slice array is built without re-assignment. - `documents-hub.tsx`: `useMemo(() => ({ count: 0 }))` used as a mutable drag counter; converted to `useRef`. - `notes-list.tsx`: `Date.now()` read during render for note-edit countdown (impure) → pinned to a `now` state ticked every 30s. - `onboarding-checklist.tsx` / `user-profile.tsx` / `user-settings.tsx`: `useEffect(() => void load(), [])` with the `load` function declared AFTER the effect — relied on hoisting, trips Compiler's "access before declared" rule. Declared inside the effect. Pattern fixes (intentional cache-via-ref → state or layout-effect): - 6 `ref.current = x` writes during render moved into layout effects (`use-realtime-invalidation`, `settings-form-card`, `inbox`). - 3 `ref.current` reads during render (search totals cache, scanner file ref) rewritten to backed-by-state. - `use-is-mobile.ts` rewritten on `useSyncExternalStore` to avoid the SSR-then-rehydrate setState dance. - `use-notifications.ts` rewritten to write socket pushes directly into the React Query cache via `setQueryData`, removing a local state mirror. Rule config (`eslint.config.mjs`): - `react-hooks/purity` → error (was warn, cleared) - `react-hooks/set-state-in-render` → error (was warn, cleared) - `react-hooks/immutability` → error (was warn, cleared) - `react-hooks/refs` → error (was warn, cleared) - `react-hooks/incompatible-library` → off (informational only) - `react-hooks/set-state-in-effect` → warn (51 remaining, all the useEffect→fetch→setState data-fetch pattern; migration to useQuery tracked in BACKLOG) Verified: tsc clean, eslint 0 errors / 69 warnings (down from 105), vitest 1315/1315, next build green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 23:14:16 +02:00
queryClient.setQueryData(UNREAD_KEY, { count: payload.count });
};
socket.on('notification:new', handleNew);
socket.on('notification:unreadCount', handleCount);
return () => {
socket.off('notification:new', handleNew);
socket.off('notification:unreadCount', handleCount);
};
}, [socket, queryClient]);
fix(compiler): React Compiler safety triage — 5 categories cleared Cleared 4 rule buckets (37 violations, including 5 real bugs) and silenced 1 informational bucket from the Next 16 / react-hooks v7 upgrade. Cleared rules promoted from `warn` back to `error` so new regressions block CI. Real bug fixes: - `interest-contact-log-tab.tsx`: `useMemo` used for side effects (5 setState calls inside a memo body); converted to `useEffect`. - `PieChart.tsx`: cumulative `let angle` mutation in a render-phase `map`; converted to `reduce` so the slice array is built without re-assignment. - `documents-hub.tsx`: `useMemo(() => ({ count: 0 }))` used as a mutable drag counter; converted to `useRef`. - `notes-list.tsx`: `Date.now()` read during render for note-edit countdown (impure) → pinned to a `now` state ticked every 30s. - `onboarding-checklist.tsx` / `user-profile.tsx` / `user-settings.tsx`: `useEffect(() => void load(), [])` with the `load` function declared AFTER the effect — relied on hoisting, trips Compiler's "access before declared" rule. Declared inside the effect. Pattern fixes (intentional cache-via-ref → state or layout-effect): - 6 `ref.current = x` writes during render moved into layout effects (`use-realtime-invalidation`, `settings-form-card`, `inbox`). - 3 `ref.current` reads during render (search totals cache, scanner file ref) rewritten to backed-by-state. - `use-is-mobile.ts` rewritten on `useSyncExternalStore` to avoid the SSR-then-rehydrate setState dance. - `use-notifications.ts` rewritten to write socket pushes directly into the React Query cache via `setQueryData`, removing a local state mirror. Rule config (`eslint.config.mjs`): - `react-hooks/purity` → error (was warn, cleared) - `react-hooks/set-state-in-render` → error (was warn, cleared) - `react-hooks/immutability` → error (was warn, cleared) - `react-hooks/refs` → error (was warn, cleared) - `react-hooks/incompatible-library` → off (informational only) - `react-hooks/set-state-in-effect` → warn (51 remaining, all the useEffect→fetch→setState data-fetch pattern; migration to useQuery tracked in BACKLOG) Verified: tsc clean, eslint 0 errors / 69 warnings (down from 105), vitest 1315/1315, next build green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 23:14:16 +02:00
return { unreadCount: data?.count ?? 0 };
}