fix(sidebar): clear provider metadata synchronously when connection changes - #848
Conversation
…hanges (libredb#846) useProviderMetadata reset the previous connection's capabilities to null inside an effect, which commits one render after the connection prop itself changes. In that one committed render, Sidebar hands ObjectTree the NEW connection paired with the PREVIOUS connection's capabilities, and useTreeNodes derives the shape of its first read from capabilities alone - so a connection switch between engines of different container depth sends one read built for the wrong engine before self-healing on the next render. Move the reset into a render-time state update (React's documented pattern for this exact case) so the mismatched pair is never committed, and add a regression test that renders a child component alongside the hook to catch it.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
cevheri
left a comment
There was a problem hiding this comment.
Measured it the way the issue was, with only your hook file swapped between the arms. On main the first object read is POST /api/db/objects/counts carrying container: [], refused with 400. On your head that call is never made: containers 200, then counts ["public"] 200.
Your test is real. Reverting only the source file fails exactly that test and leaves the other 17 green, with the precise pair #846 describes: conn-2 carrying conn-1's supportsInlineRowEdit.
The equal-depth control the issue asked for holds too. PostgreSQL to MySQL still issues containers and counts within about 120 ms of the metadata answer, so the correctness cost no slower switch.
Main has moved since you branched, so I test-merged and ran the whole suite on the result: 543 files, 543 pass.
One aside, no change wanted: readKey carries attempt, so the render branch fires on a retry as well and the two reset lines left in the effect are redundant. The new comment says the opposite. I will adjust that sentence myself.
Merging this. Thank you.
|
small notes for your next PR: please write a comment on releated-issue(#846 ), before create PR |
Fixes #846.
Root cause
useProviderMetadatareset the previous connection'smetadata(and thus itscapabilities) tonullinside auseEffect. That effect only fires afterReact commits the render, but the
connectionprop it's keyed on changesduring the render itself. So for exactly one committed render, this hook
still returns the previous connection's capabilities while
connectionalready points at the new one.
Sidebarpasses both straight through toObjectTree:and
useTreeNodesderives the shape of its very first read (depth, viacontainerDepth(capabilities)) fromcapabilitiesalone — it has no way toknow those capabilities don't belong to
connectionyet. When the twoengines have different container depths, that one render is enough to issue
a root read shaped for the wrong engine, which the route correctly refuses
with HTTP 400 before the next render corrects it.
Fix
Move the reset from the effect into a render-time state update — React's own
documented escape hatch for "reset some state when a prop changes": calling
setStateconditionally during render discards that render's output andre-runs the component immediately with the cleared state, so the mismatched
(new connection, old capabilities)pair is never committed to the DOM orhanded to a child. The effect still exists for the async fetch itself; the
render-time branch only pulls the "clear it now" half one render earlier.
Testing
Added a regression test in
tests/hooks/use-provider-metadata.test.tsthatmounts a real child component alongside the hook (rather than only asserting
on
result.currentviarenderHook, which only observes state once Reacthas already flushed effects — exactly the render this bug disappears from).
It fails on
main— the child is rendered once with the new connection's idpaired with the previous connection's
supportsInlineRowEdit— and passesafter the fix.
Ran the full local gate:
format,lint,typecheck,knip, all fourdrift-check scripts, the full test suite (537 files / 17469 passing), the
100% line-coverage gate, and
build— all green.I did not attempt the issue's "open question" about read-ordering (whether a
late-arriving refusal could overwrite an already-filled slot) — this fix
removes the mismatched read entirely, so that ordering question no longer
has anything to land on for this path.