Ask
152
@sam_the_temp ·

coming from useEffect fetching, is one query key per screen too coarse Hooks

Two months into my first React job. The codebase I inherited fetches in useEffect everywhere and I am moving it to TanStack Query one screen at a time.

My instinct was to give each screen one key - ['ordersScreen'], ['customerScreen', id] - because then invalidation is easy and I always know what to invalidate. A senior on another team looked at it and said "that is not what keys are for" and then went to a meeting.

So: what is the actual convention, and why is screen-shaped wrong?

9 answers Share
Report

Answering anonymously — a moderator will review it first.

  • @mulch_marta · 3w ago · 3 replies

    Key by the resource and its parameters, not by the place it is displayed. ['orders', { status, page }] and ['customer', id].

    The reason is that screens are not unique owners of data. The moment two screens both show the customer, screen keys give you two copies that go stale independently, and updating one leaves the other wrong. Resource keys give you one entry that both screens observe, so a single invalidation fixes both, and the second screen renders instantly from cache instead of refetching.

    Invalidation stays just as easy because matching is by prefix: invalidateQueries({ queryKey: ['orders'] }) hits every orders query regardless of the filters after it. That prefix behaviour is the whole design and it is what screen keys throw away.

    121
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
    • @mulch_marta · 4w ago

      Exactly that, and it is why the resource goes first in the key and the specifics after. Once you internalise "keys are paths, invalidation matches prefixes", most of the design questions answer themselves.

      57
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report
    • @sam_the_temp · 3w ago

      So invalidateQueries({ queryKey: ['orders'] }) after a mutation hits both the list and the detail without me listing them? That is the bit I did not realise I was giving up.

      43
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report
  • @film_fridge · 4w ago

    One caveat to "never screen-shaped": if you have a genuinely composite endpoint - /dashboard/summary returning six unrelated things because it exists for one screen - then keying it after that screen is honest. The rule is key by what the request identifies. Usually that is a resource. Occasionally the endpoint really is screen-shaped, and pretending otherwise is worse.

    46
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
  • @torque_spec · 4w ago · 2 replies

    Concrete convention that scales past about ten screens: put the keys in one file per resource, as a factory, and never write a key literal in a component.

    export const orderKeys = {
      all: ['orders'] as const,
      list: (f: Filters) => [...orderKeys.all, 'list', f] as const,
      detail: (id: string) => [...orderKeys.all, 'detail', id] as const,
    }
    

    It sounds like ceremony for a small app and it pays for itself the first time you need to find every place that reads orders. It also makes the prefix relationships explicit instead of a convention people forget.

    84
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
    • @bobbin_bind · 3w ago

      The as const matters more than it looks if you are on TypeScript - without it the key widens to string[] and you lose every bit of help the compiler could have given you about which invalidation matches which query.

      36
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report
  • @sdcard_sid · 4w ago

    Also: you are doing the right thing migrating a screen at a time rather than all at once. Something nobody warns you about - a half-migrated screen where one thing uses the query cache and its sibling still fetches in useEffect will produce a very confusing class of bug where refreshing fixes half the page. Finish a screen completely before moving on, even if that means the ugly effect stays for another week.

    58
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
  • @flashcard_fen · 3w ago

    Whoever said "that is not what keys are for" and walked off owed you two more sentences. Worth going back to them, both because the answer is short and because you want that person answering the next question rather than dropping a verdict.

    32
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
  • @pivot_pilot · 3w ago

    Being two months in and rewriting the effects rather than leaving them alone puts you ahead of a depressing number of people with five years on their profile.

    17
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report