Multi-category notification center: Firestore + RTDB in the same drawer
How to pick the right database per notification category, and combine a single-read Firestore query with real-time RTDB listeners in the same panel, without conflicts.
The problem
A notification center with multiple categories (leave requests, calendar, chat, reminders) often has data living in different databases with different characteristics: some need a one-time read, others need real-time updates. Using the same pattern for everything wastes reads or introduces unnecessary listeners.
Firestore: single read with multiple filters
For categories where history doesn't change often (e.g. leave requests), a Firestore query with
.get() — not .onSnapshot() — avoids keeping an unnecessary listener open.
const cutoff = Date.now() - 90 * 24 * 60 * 60 * 1000; const tipi = [ 'ferie_richiesta', 'ferie_approvata', 'ferie_rifiutata', 'ferie_annullata' ]; db.collection('activityLog') .where('operatore', '==', currentUser) .where('tipo', 'in', tipi) .where('ts', '>=', cutoff) .get() // una sola lettura, non real-time .then(snap => snap.docs.map(d => ({ id: d.id, ...d.data() })));
RTDB: real-time listeners on existing nodes
Calendar, reminders and chat instead live on the Realtime Database — small nodes, already structured
for fast reads. Attaching with .on('value') to existing nodes doesn't add significant cost,
and it's the right choice when data changes often and the user needs to see updates without refreshing.
const ref = db.ref('calendario/' + reparto); ref.on('value', snapshot => { const eventi = snapshot.val() || {}; // Filtra: solo eventi già scattati, creati dall'utente o del suo reparto const visibili = Object.entries(eventi) .filter(([id, ev]) => ev.firedAt && (ev.creatoDa === currentUser || ev.reparto === userReparto) ); renderNotificheCalendario(visibili); }); // Da chiamare allo smontaggio del componente / cambio utente function cleanup() { ref.off('value'); }
Rule of thumb: data that changes rarely and is just consulted → Firestore .get(). Data that must update live in the UI without user action → RTDB .on('value').
Watch the bootstrap: in a PWA with "remember me", real-time listeners started at manual login must also be started in the session-restore path — otherwise half your users end up with notifications silently disabled after a refresh.