BXUP Track progress & test yourself — free
Frontend & Mobile · Playbook · Core · ~4 min read
FRONTEND & MOBILE · PLAYBOOK · CORE

Refactor State and Data Layers Behind Characterization Tests

Prop-drilling, ad-hoc fetch effects, and God-context files are ideal AI refactor targets — mechanical, wide, and boring. But state refactors break apps silently. So AI first maps who reads and writes what, tests pin today's behavior, and only then does the migration proceed, one slice at a time.

Tier: Core · Workflow area: Component development · Tools: Claude Code, TanStack Query, Zustand / Redux Toolkit

The trap in AI-assisted refactoring is speed without a safety net: the model happily rewrites forty files in one pass, everything compiles, and a week later someone finds the cart badge no longer updates. Characterization tests — tests that pin current behavior, bugs and all — turn "it compiles" into "it still behaves."

When to use

  • Prop-drilling runs three-plus levels deep and every feature change touches a chain of pass-through props.
  • Data fetching is ad-hocuseEffect + fetch + local loading flags copy-pasted across components, no caching, duplicate requests.
  • A monolithic context or store re-renders the world on every keystroke.
  • Migrating librarieslegacy Redux to Redux Toolkit, context soup to Zustand, hand-rolled fetching to TanStack Query.

When NOT to use

  • No test runner in the reposet up Vitest/RTL first; this playbook is meaningless without executable pins.
  • Behavior is supposed to change simultaneouslyrefactor and feature work in one pass makes failures unattributable; sequence them.
  • The pain is localizedone component with clumsy state doesn't need a data-layer migration; just fix the component.

The recipe

  1. Have AI produce the state map — read-only, with citations. Before touching anything:

    Read-only task, modify nothing. For the checkout feature
    (src/features/checkout/), map the state and data layer:
    1. Every piece of client state: where it lives, every reader, every
       writer — file:line for each.
    2. Every server interaction: component, trigger, caching behavior
       (or none), duplicate-fetch risks.
    3. Prop-drilling chains 3+ levels deep, as component paths.
    4. Rank the 5 riskiest spots to refactor and say why.
    Output as a table I can paste into the PR description.
    

    Spot-check three citations by opening them. A wrong map means a wrong refactor — do not skip the audit (same discipline as any comprehension pass).

  2. Pin current behavior with characterization tests. These assert what the app does today, including its quirks:

    Write characterization tests for the checkout flow pinning CURRENT
    behavior — do not fix anything you find:
    - add item -> cart badge count and subtotal update
    - shipping option change -> total recalculates (pin the rounding!)
    - refresh mid-checkout -> which state survives, which resets
    - two tabs -> document the current (broken?) sync behavior as a test
      with a TODO comment, not a fix
    Use RTL + MSW with our existing handlers. These tests must pass on
    main BEFORE any refactor commit.
    
    npx vitest run src/features/checkout --reporter=verbose
    

    Good output asserts through user-visible surfaces (roles, text) not implementation internals — the tests must survive the refactor unchanged; that is their entire point.

  3. Design the target shape yourself; let AI critique it. You decide: server state → TanStack Query; client/UI state → scoped Zustand stores (or RTK slices); truly-local state stays in components. Write it as a half-page plan, then:

    Here is my target architecture (attached). Against the state map,
    list: state I've misclassified (server vs client vs local), places
    where optimistic updates will fight query invalidation, and any
    reader/writer the plan orphans.
    
  4. Migrate slice by slice, tests green between every slice. One commit per slice, never "big bang":

    Slice 1: migrate cart server-state to TanStack Query. Create
    useCartQuery / useAddItemMutation (optimistic, rollback on error) in
    src/features/checkout/api.ts, delete the useEffect fetches in
    CartPanel and MiniCart, remove the drilled cartItems/setCartItems
    props. Do NOT touch shipping or payment state. Characterization
    tests must pass unmodified: run npx vitest run src/features/checkout
    and show the output.
    

    The loop for each slice: agent migrates → runs the pinned tests → all green → commit → next slice. If a test goes red, the agent explains the diff instead of editing the test:

    git add -p && git commit -m "checkout: cart server-state -> TanStack Query (slice 1/4)"
    
  5. Review the semantic risk points by hand. Compilers and pinned tests miss four things — check them personally: stale-closure captures in moved callbacks, changed re-render scope (profile a keystroke in React DevTools Profiler before/after), cache lifetime semantics (staleTime/gcTime vs. the old always-refetch behavior — deliberate, not default), and event ordering around optimistic updates.

  6. Only now, fix the quirks you pinned. The TODO-tagged tests from step 2 (the broken tab sync, the rounding oddity) become deliberate behavior changes in their own PR, with the test updated to the new intended behavior — visible, reviewed, and attributable.

Remember:

In a refactor, a failing characterization test is information and an edited characterization test is a cover-up. The agent may never "fix" a pinned test to make its refactor pass — that rule, stated in the prompt and enforced in review, is what makes AI-scale refactoring safe.

Make it yours

  • React (as written)TanStack Query + Zustand is the modern default; for existing Redux shops, RTK + RTK Query keeps one mental model, and the recipe is identical with createApi replacing TanStack Query.
  • Vue 3Pinia stores for client state, TanStack Query Vue for server state; characterization tests in Vitest + Vue Testing Library; the state-map prompt swaps "context" for "provide/inject and global reactives."
  • Angularsignals + @ngrx/signals (or NgRx) for client state, services with RxJS or TanStack Query Angular for server state; pin behavior with component-harness tests before converting subject-soup services.
  • React Nativeidentical libraries; add two pins before migrating: app-background/foreground refetch behavior and offline mutation queueing, both easy to regress invisibly.

Quality check

  • State map verifiedthree-plus citations opened by hand before any edit.
  • Characterization tests passed on main first and pass unmodified after every slice.
  • One slice per commitgit log reads as a sequence of reversible steps, any of which can be reverted alone.
  • Re-render scope profiledbefore/after Profiler traces for one hot interaction attached to the PR.
  • Behavior changes shipped separatelyquirk fixes are their own PR with updated tests, never smuggled into the refactor.
Key Takeaway:

Map first, pin behavior second, migrate in slices third — with the iron rule that the agent never edits a characterization test to go green. That sequencing is what lets you refactor at AI speed without discovering the breakage in production.

This is the free reading view. Open BXUP to track your progress, run the hands-on labs, and prove it with original, exam-pattern assessments.
© Anvi Innovate · BXUP — Be Exponentially Upskilled · BXUP is an independent learning platform, not affiliated with or endorsed by AWS, Microsoft, Google, NVIDIA, IBM, Salesforce, Databricks, Oracle or Anthropic. All trademarks belong to their respective owners. · bxup.in