Theme.setLoadedStyleName() is called even when the style load no-ops — one bad first-mount permanently disables a component's CSS for the whole session
#4835
Unanswered
GiorgiLeki
asked this question in
PrimeVue
Replies: 1 comment
|
Thank you! I'll check it. |
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Environment
5.0.0-rc.25.0.0-rc.23.5.30unstyled, nocssLayer)4.5.5(slightly different symptom — see "v4 vs v5")Summary
In styled mode, component CSS is injected lazily on first mount. If the theme
CSS for a component resolves empty at that exact moment,
BaseStyle.load()silently returns
{}and injects nothing — but_loadThemeStyles()marks thecomponent's style name as loaded regardless.
Because the flag is global and checked before every subsequent load, no later
instance of that component ever retries. The component then renders with no CSS
for the rest of the session, application-wide — not just in the subtree that
triggered it. There is no error, no warning, and nothing in DevTools except a
missing
<style>tag.Root cause
@primevue/core/basecomponent/index.mjs→_loadThemeStyles():@primevue/core/base/style/index.mjs→BaseStyle.load():So the two lines disagree:
load()treats "nothing to inject" as a legitimateno-op and returns
{}, while_loadThemeStyles()treats having called it asproof it succeeded. Once
setLoadedStyleNameruns, the guard on the next mount(
!Theme.isStyleNameLoaded(name)) is false forever._loadCoreStyles()has the same shape (Base.setLoadedStyleName(...)after aloadCSS()that can no-op the same way).The empty-CSS condition is the trigger; the unconditional flag is what makes it
permanent and global. Even if the underlying emptiness is a legitimate race,
the next mount should be able to recover — today it cannot.
Impact
session, including routes that never touch the offending component tree.
that triggered it, so it reads as "this one screen is unstyled" and is very
easy to misfile as a CSS/theme bug.
Reproduction (from our app — verified 2026-07-16)
Two fresh sessions, identical build, differing only in which component mounts
the session's first
InputNumber:InputNumberto mountstyle[data-primevue-style-id="inputnumber-style"].p-inputnumbercomputeddisplay:block,position:static— unstyleddisplay:flex,position:relative— correctAfter Route A, navigating anywhere else in the SPA and mounting more
InputNumbers does not recover: the tag is never created, becauseinputnumberis already flagged loaded.Check in DevTools:
What churns in our Route A dialog (we believe this is what makes
getComponentTheme()/the computed style resolve empty at that instant, but wehave not proven the emptiness itself — see "What we could not determine"):
<template v-if="syncFn($form)" />pattern that calls a function duringrender, and that function mutated reactive refs;
InputNumbers rendered inside av-forover a computed derived from thosesame refs — so child count changes during the initial render pass.
We consider (1) our own anti-pattern and have moved that logic into watchers.
We're reporting the framework behaviour regardless: an app-level render
mistake should not be able to permanently disable a component's CSS
process-wide with no diagnostic.
Suggested fix
Only record the style name as loaded when something was actually injected.
useStyle()already returns anisLoadedref, andload()returns{}on theno-op path, so the information is available:
Alternatives, in rough order of preference:
self-heal. Same treatment for
_loadCoreStyles.load()explicit about the no-op — return{ isLoaded: ref(false) }instead of a bare
{}, so callers can't mistake it for success.getComponentTheme()yields empty CSS for a namedcomponent. Even without a behaviour change this turns a silent, session-wide
failure into a one-line clue.
v4 vs v5
<style data-primevue-style-id="inputnumber-style">tag wascreated, but with empty content.
load()short-circuiting before
useStyle).Same root cause and same permanence; only the artifact differs. Any workaround
that probes for the tag must therefore check both existence and content.
What we could not determine
trigger to the render churn above but haven't instrumented
Theme/getComponentTheme()to show what it returns during the bad pass. If you canpoint at where the theme is expected to be resolvable relative to
_loadThemeStyles, we're happy to dig further.InputNumber, but nothingin the mechanism is InputNumber-specific — any component whose first mount
lands in a churning subtree should be able to poison itself the same way.
Our workaround (for anyone who lands here)
Re-invoke the loader manually when the tag is missing or empty, using PrimeVue's
own style generator so the output matches what the component would have injected:
This uses
primevue/<component>/style+ the loader'smanualoption — bothsemi-internal, which is precisely why we'd rather have the fix upstream.
Before filing
ref-mutating function from a
<template v-if="fn()" />+InputNumbers ina
v-forover a computed of those refs. Confirm the tag is absent and thata second
InputNumberon another route stays unstyled.5.x(we are onrc.2).setLoadedStyleName/isStyleNameLoaded/"styles not applied" before opening — the MCP docs mirror carries no issue
or changelog data, so this needs checking on GitHub directly.
All reactions