refactor(client): drop the dead loader-data guards in three screens - #4446
Open
gilgardosh wants to merge 2 commits into
Open
refactor(client): drop the dead loader-data guards in three screens#4446gilgardosh wants to merge 2 commits into
gilgardosh wants to merge 2 commits into
Conversation
The charge, business and contracts screens each wrapped `useLoaderData()` in a try/catch with a `react-hooks/rules-of-hooks` disable, guarding against being rendered outside a data router. None of them can be: each is only ever a lazy route element of `createBrowserRouter`, and none has a JSX call site anywhere. `contracts.tsx` goes further — its route declares no loader at all, so `loaderData` was always undefined there and every branch reading it was unreachable. Deleted rather than rewritten. `charge.tsx` also loses a mount effect that re-executed a query already unpaused under the identical condition. I had expected this to be costing a second request; it was not. urql dedupes the re-execution against the still-in-flight operation, which the new test establishes by counting what a scripted `fetch` actually receives. The effect was redundant, not expensive, and the test now holds that count at one across the change. The test drives a `createMemoryRouter`, not a `MemoryRouter`: `useLoaderData` throws outside a data router. That is the thing the try/catch was really guarding, and it is why removing it is safe only for components that are exclusively route elements — an earlier draft of this test used `MemoryRouter` and failed for exactly that reason. `useRouteLoaderData` is deliberately not used: no route in `router/config.tsx` declares an `id`, and it would need one. Step 5 of the urql quick-wins sequence. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TbrGL3NndzRiEwJkwHxnbm
gilgardosh
temporarily deployed
to
accounter-fullstack
September 10, 2026 17:27 — with
GitHub Actions
Inactive
gilgardosh
temporarily deployed
to
accounter-fullstack
September 10, 2026 17:27 — with
GitHub Actions
Inactive
This was referenced Sep 10, 2026
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
One or more issues must be addressed before approval.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Simplifies route-only screen data loading and removes redundant query re-execution.
Changes:
- Removes
try/catchloader guards from charge and business screens. - Removes unreachable loader-data handling from contracts.
- Adds a charge request-count regression test and changeset.
File summaries
| File | Description |
|---|---|
| packages/client/src/components/screens/charges/charge.tsx | Updated as part of this pull request. |
| packages/client/src/components/screens/charges/tests/charge.test.tsx | Updated as part of this pull request. |
| packages/client/src/components/screens/businesses/clients/contracts/contracts.tsx | Updated as part of this pull request. |
| packages/client/src/components/screens/businesses/business.tsx | Updated as part of this pull request. |
| .changeset/urql-loader-data-guards.md | Updated as part of this pull request. |
Review details
- Files reviewed: 5/5 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
The comment still claimed the removed mount effect cost "two round-trips". It did not: urql dedupes the re-execution against the operation still in flight, so the scripted fetch saw one request before the change as well as after. I had corrected this in the changeset and PR description but left the comment saying the opposite, which would have taught the next reader the wrong reason for the cleanup. The assertion holds the count at one across the removal; it does not record a fix. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TbrGL3NndzRiEwJkwHxnbm
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Step 5 of 10 in the urql quick-wins sequence — tracking doc in #4437.
What changed
The charge, business and contracts screens each wrapped
useLoaderData()in atry/catchwith an// eslint-disable-next-line react-hooks/rules-of-hooks, guarding against being rendered outside a data router. None of them can be — each is only ever a lazy route element ofcreateBrowserRouter, and none has a JSX call site anywhere in the app (verified by grep).contracts.tsxgoes further. Its route declares no loader at all — onlychargeLoaderandbusinessLoaderexist — soloaderDatawas alwaysundefinedthere and every branch reading it was unreachable. Deleted rather than rewritten.charge.tsxalso loses a mount effect that re-executed a query already unpaused under the identical condition.Two corrections to what I claimed when planning this
Both came out of writing the test first, and both are worth reading before approving:
1. The effect was not costing a second request. I said in the plan that it caused "a duplicate network round-trip on every mount." It doesn't — urql dedupes the re-execution against the still-in-flight operation. The new test establishes this by counting the operations a scripted
fetchactually receives: it was already 1 before the change. So this is a redundancy cleanup, not a performance fix, and the test's job is to hold that count at one across the removal.2. The
try/catchwas not entirely dead code.useLoaderData()genuinely throws outside a data router. My first draft of the test usedMemoryRouter— which is not a data router — and the suite went red withuseLoaderData must be used within a data router. The test was modelling an unsupported configuration, not finding a bug.Rewritten against
createMemoryRouter+RouterProvider, which is what these components actually live inside. That is also precisely why removing the guard is safe only for components that are exclusively route elements — which is the condition I verified for all three.Testing
New
charge.test.tsxusing the scripted-fetchreal-Clientharness fromcharges-table-refetch.test.tsx. The route deliberately declares no loader, which is what leavesuseLoaderDataundefined and lets the query run at all.Note for later
useRouteLoaderDatais deliberately not used — no route inrouter/config.tsxdeclares anid, and it would need one.Separately, worth a follow-up: since the
:chargeIdroute always has a loader,charge.tsx'suseQueryis permanently paused in production, and itschargeIdprop has no callers. The whole query path there looks unreachable. Out of scope here, but I've noted it in the tracking doc.🤖 Generated with Claude Code
https://claude.ai/code/session_01TbrGL3NndzRiEwJkwHxnbm
Generated by Claude Code