Personal Catalogue: React & Firebase — Chapter 7, Exercise 3 ===================================================================== TASK Explain precisely why switching to onSnapshot() fixes the stale-tags bug without needing any change to TagEditor, ItemDetail, or AddItemForm — and name the one real, ongoing cost that switch introduces which a one-time getDocs() call never had. SOLUTION TagEditor never talks to React state directly at all — it calls addTag()/removeTag(), which call updateDoc() against Firestore itself. Whether the rest of the app finds out about that change was never TagEditor's own responsibility; it only ever needed to make the write correctly, which it already did under both the getDocs() and onSnapshot() versions. What changes is entirely on the reading side. Under getDocs(), the items array in App is populated exactly once, at mount, from a single snapshot in time — nothing after that point ever tells React that a document underneath it has changed, so items keeps holding the same values forever, no matter how many real writes happen. Under onSnapshot(), the exact same items array is instead attached to a live subscription: Firestore itself calls the provided callback again, automatically, the instant any document in the items collection changes for any reason, including a change made by this very tab's own TagEditor. That re-invocation is what calls setItems() with a fresh snapshot — and because ItemDetail receives item as a prop derived fresh from items on every render (selectedItem = items.find(...)), it automatically reflects the new tags the next time it renders, with zero code inside ItemDetail or TagEditor needing to know a listener even exists. The same reasoning covers AddItemForm: it only ever needed to call addDoc() correctly. Whether a newly created document shows up in the list was always a question about how items gets populated, and onSnapshot() answers that question the same way for every kind of write — additions, tag edits, anything — without treating any one of them as a special case needing its own callback. The real, ongoing cost: a live listener stays open and keeps counting against Firestore's own per-document-read billing for as long as it's subscribed — the initial snapshot reads every document once, and it reads again every time something changes, for as long as the component stays mounted. A one-time getDocs() call reads once and is done; it has no ongoing cost at all once the promise resolves. The listener also has to be explicitly unsubscribed when the component unmounts, or it keeps running (and keeps costing reads) even after nothing is using it anymore. WHY THIS WORKS AS AN ANSWER ---------------------------- It correctly separates the write side (unchanged, always correct) from the read side (the actual source of the bug and the fix), explains exactly why a fresh prop value naturally reaches ItemDetail once items updates, and names a real, specific, ongoing cost (continuous per-change read billing plus the need to unsubscribe) rather than a vague "it uses more resources" answer.