Skip to content

fix(relation): retry menu options after a failed query instead of caching the failure - #7987

Draft
martinjagodic wants to merge 3 commits into
mainfrom
fix/relation-menu-options-retry
Draft

fix(relation): retry menu options after a failed query instead of caching the failure#7987
martinjagodic wants to merge 3 commits into
mainfrom
fix/relation-menu-options-retry

Conversation

@martinjagodic

@martinjagodic martinjagodic commented Sep 11, 2026

Copy link
Copy Markdown
Member

Summary

A relation dropdown could show "No options" until you typed something, and stay that way for the life of the page — even once the backend had recovered. Typing worked because it used a different cache key.

Reported downstream at Mestna-obcina-Celje/moc-www#740, where it affected every relation field on the site and several editors at once.

Root cause

Three defects compounding, all on main:

  1. A failed query was indistinguishable from an empty collection. The query thunk never rejects — on failure it resolves with the action queryFailure returns, which carries payload.error where a success carries payload.hits. RelationControl read result.payload.hits || [], so a failed request became the claim that the collection has no entries.
  2. RelationCache then memoised that claim for the life of the page. It only stores resolved values, so making failures reject is enough to fix it — but nothing was rejecting.
  3. The one that made it permanent: react-select's defaultOptions={true} loads exactly once, in a mount effect with no dependencies, and there is no way to ask it to try again. Even with 1 and 2 fixed, the menu would have stayed empty.

The change

  • hitsFromQueryResult throws on a failed query, so the cache never stores it and the next attempt genuinely retries.
  • The component owns the menu list (menuOptions state) and passes it as an array rather than letting react-select load it once. onMenuOpen retries when the list has never loaded.
  • menuOptions === undefined ("never loaded") is deliberately distinct from [] ("loaded, genuinely empty") — only the former retries, so a truly empty collection is not re-queried on every open.

Two consequential fixes:

  • shouldComponentUpdate ignored state entirely, so no setState could repaint the menu on its own — it only ever rendered because queryHits happened to change in the same tick. It now considers state when it is given it: the core Widget borrows this method off the control instance and calls it with nextProps alone (EditorControlPane/Widget.js:92), so nextState is genuinely absent about half the time and must not be dereferenced.
  • getSelectedOptions returns null for a field with no value, previously unreachable because the initial load was gated on the field already having one.

isLoading is now passed explicitly, since react-select had been deriving it from defaultOptions={true}. Without it a slow load renders as "No options" — the exact confusion being fixed.

Request count is unchanged or slightly lower: the mount-time empty-term query used to come from react-select and now comes from the control, and the redundant second call for fields that already have a value is gone.

Testing

  • 27 tests in the package pass (5 new), including the cache contract, the retry-on-open, the loading state, and a regression test that calls shouldComponentUpdate with a single argument the way Widget.js does.
  • Full suite green: 113 suites / 1391 tests, tsc --noEmit clean, prettier and eslint clean.
  • Verified in a browser against a production site using this widget: all four relation dropdowns populate on click without typing.

Draft

Opened as a draft: this touches a widget every Decap user has, and I would like a second pair of eyes on the shouldComponentUpdate contract in particular before it lands.

Caught by CI's `lint:format` gate, which runs prettier separately from
eslint — eslint was clean, which is why this was missed locally. Two
line-wrapping changes in the test file, no behaviour.
@netlify

netlify Bot commented Sep 11, 2026

Copy link
Copy Markdown

Deploy Preview for decap-cms ready!

Name Link
🔨 Latest commit 5a476b6
🔍 Latest deploy log https://app.netlify.com/projects/decap-cms/deploys/6aa3fbbb76d3ca0008bfcbab
😎 Deploy Preview https://deploy-preview-7987--decap-cms.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@github-actions github-actions Bot added the type: bug code to address defects in shipped code label Sep 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type: bug code to address defects in shipped code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant