Marking Items Used

Food Tracker (React + Firebase)

Chapter 9 · Marking Items Used

A short chapter, and a satisfying one — this is where Chapter 2's original data-modeling decision finally gets exercised as a real transition, not just a starting shape.

The Transition: Active to Used

Chapter 2 modeled a used item as one with status: "used" and no expiryDate field at all — not a null value, a genuine absence. Marking an active item used has to actually produce that same shape, which means removing a field on an update, not merely creating a new document without it in the first place.

import { doc, updateDoc, deleteField, serverTimestamp } from "firebase/firestore"; async function markItemUsed(itemId) { const itemRef = doc(db, "items", itemId); await updateDoc(itemRef, { status: "used", usedAt: serverTimestamp(), expiryDate: deleteField(), }); }

Why Not Just Set expiryDate to null?

Setting it to null would leave the field genuinely present, with a null value — a different shape from the one Chapter 2 designed, and a different shape from a used item that started out used from the very beginning (Chapter 2's own creation example never included the field at all). Using deleteField() keeps both paths to "used" — created used, or transitioned to used — producing the identical document shape, rather than the app quietly having two different ways of representing the same "no expiry" state depending on how an item got there.

Extending Chapter 6's Rules to Cover This Update

Chapter 6 validated shape at create time and left update/delete permissive until real auth exists (Chapter 11). That's still the right call for who can update — but rules can validate the shape of an update independently of who's making it:

allow update: if request.resource.data.status in ['active', 'used'] && (request.resource.data.status != 'used' || !('expiryDate' in request.resource.data));

This says: if the update's own resulting document has status == 'used', that same resulting document must not contain an expiryDate field at all. It's not an authorization check — anyone can still perform this update, per Chapter 6's own deferred-to-Chapter-11 stance — it's a data invariant, enforced at the database layer regardless of which client code path triggered the update, or whether that code even remembered to call deleteField() correctly.

Rules validate transitions, not just creation
Chapter 6 only showed rules constraining what a brand-new document could look like. This chapter's own rule constrains what an update is allowed to turn a document into — a genuinely different kind of check, comparing the resulting shape against an invariant rather than checking who's allowed to act. Security Rules aren't only about authorization; they're a real place to enforce that the data itself stays internally consistent, no matter what code path produced the write.
deleteField() needs a merge, not a replace
deleteField() works inside updateDoc(), and inside setDoc(ref, data, { merge: true }) — but not a plain, non-merge setDoc(), which replaces the entire document outright. "Delete this one field" only means something when the rest of the document is being preserved around it.
An "undo" feature would need to restore the expiry date too
If this app ever adds an undo action for an accidental "mark used," simply flipping status back to "active" is not enough — the item would then be active with no expiryDate at all, since deleteField() already removed it and undoing the status change doesn't bring it back. A genuine undo needs to either have retained the original expiry date somewhere to restore, or explicitly ask the user for a new one.

Where This Course Is Headed

Recipe lookup for items nearing expiry, Firebase Authentication (which finally tightens the update/delete rules this chapter left permissive), deployment, and a capstone.

Hands-On Exercises

Exercise 1

Explain why marking an item used calls deleteField() on expiryDate rather than setting it to null, tying the reason back to Chapter 2's own data model.

📄 View solution
Exercise 2

Explain what invariant this chapter's own update rule enforces, and how it differs in kind from Chapter 6's create rule — one validates creation shape, the other validates something else. What is that something else?

📄 View solution
Exercise 3

Explain what would go wrong if a future "undo" feature simply flipped status back to active without also restoring expiryDate, and why deleteField() is the reason this specific problem exists.

📄 View solution

Chapter 9 Quick Reference

  • deleteField() — removes a field entirely on update, matching Chapter 2's "absence, not null" design
  • Consistent shape — created-used and transitioned-to-used items end up identical, not two different representations of "no expiry"
  • Update-time rules — Security Rules can validate a transition's resulting shape, not just a new document's creation shape
  • A data invariant, not an auth check — this chapter's rule enforces "used items never have expiryDate," regardless of who performs the update
  • deleteField() needs a merge — works in updateDoc() or a merged setDoc(), not a plain replace
  • Next chapter: Recipe Lookup with TheMealDB