What happened?
Theme changes that fire while a subtree is frozen by react-freeze (react-navigation freezeOnBlur, manual <Freeze>) are permanently missed by every JS-resolved styling path — withUniwind, accent props (tintColorClassName etc.), and useUniwind — until the affected component re-renders or remounts for an unrelated reason.
Verified on uniwind-pro 1.6.0: the suspended-trees support from 1.0.0-rc.7 works — className styles on built-in components are correct on frozen screens after a flip. But the JS paths stay stale:
withUniwind-wrapped components (vector icons, third-party text components) keep the old theme's colors
- accent props (
tintColorClassName on Image) keep the old tint — the ref re-attach re-links the shadow node on thaw, but the stale render-time tintColor prop remains on screen; the relink doesn't catch the accent up
useUniwind() returns the pre-flip theme forever
useCSSVariable / useResolveClassNames are correct after thaw (they re-sync in their effect bodies)
Mechanism: react-freeze hides via Suspense; React tears down the hidden tree's layout effects and re-creates them on reveal without re-rendering. The three broken paths resolve during render and subscribe in a useLayoutEffect whose body only re-subscribes — a notification that fired while the effects were torn down is never replayed (hoc/withUniwind.native.tsx, hooks/useUniwind.ts; accents via getAccentColor in render). The two healthy hooks heal precisely because their effect body re-syncs (useResolveClassNames.native.ts → recreate(), useCSSVariable.ts → updateValue()). The same gap drops a change landing between initial render and subscribe.
Suggested fix: give the broken paths the same effect-body re-sync the healthy hooks have (guarded to no-op on first mount, mirroring useCSSVariable's isMountRef), or move the subscriptions to useSyncExternalStore, which rides the passive-effect path React keeps connected under a suspended Suspense boundary. For accents, additionally catch the value up when a shadow node re-links.
This is the report Brentlok asked for in #466 ("If you have similar issue for oss version please create a separated issue with reproduction") — the hook sources are identical in OSS 1.11.0, so it applies there too.
Steps to Reproduce
import { useState } from "react"
import { Button, Image, Text, View } from "react-native"
import { Freeze } from "react-freeze"
import { Uniwind, useCSSVariable, useUniwind } from "uniwind"
const Probe = () => {
const { theme } = useUniwind()
const background = useCSSVariable("--color-background")
return (
<View className="bg-background p-4">
<Text className="text-foreground">className ink (heals — Pro engine)</Text>
<Image source={someGlyph} tintColorClassName="accent-foreground" />
{/* ^ stays on the old tint after thaw */}
<Text className="text-foreground">useUniwind: {theme} (stays stale)</Text>
<Text className="text-foreground">useCSSVariable: {String(background)} (heals)</Text>
</View>
)
}
export const Repro = () => {
const [frozen, setFrozen] = useState(false)
return (
<View className="flex-1 items-center justify-center gap-4 bg-background">
<Button title="1. freeze" onPress={() => setFrozen(true)} />
<Button
title="2. flip theme"
onPress={() =>
Uniwind.setTheme(Uniwind.currentTheme === "dark" ? "light" : "dark")
}
/>
<Button title="3. unfreeze" onPress={() => setFrozen(false)} />
<Freeze freeze={frozen}>
<Probe />
</Freeze>
</View>
)
}
- Press "freeze", then "flip theme", then "unfreeze".
- Expected: everything reflects the new theme after unfreezing.
- Actual: the
useCSSVariable line and className styles are correct; the tinted image and the useUniwind line keep the old theme indefinitely (further freeze/thaw cycles stay stale; only a remount, or another flip while visible, repairs them). A withUniwind(AnyIcon) behaves like the tinted image.
- Same result with two tabs +
freezeOnBlur: true, flipping the theme from the other tab. In a real app the staleness looks intermittent because list recycling remounts some elements and not others (React Compiler element caching removes the parent-re-render repair, making it sticky).
Snack or Repository Link (Optional)
No response
Uniwind version
uniwind-pro 1.6.0
React Native Version
0.85.3
Platforms
iOS, Android
Expo
Yes
Additional information 〰
What happened?
Theme changes that fire while a subtree is frozen by react-freeze (react-navigation
freezeOnBlur, manual<Freeze>) are permanently missed by every JS-resolved styling path —withUniwind, accent props (tintColorClassNameetc.), anduseUniwind— until the affected component re-renders or remounts for an unrelated reason.Verified on uniwind-pro 1.6.0: the suspended-trees support from 1.0.0-rc.7 works — className styles on built-in components are correct on frozen screens after a flip. But the JS paths stay stale:
withUniwind-wrapped components (vector icons, third-party text components) keep the old theme's colorstintColorClassNameonImage) keep the old tint — the ref re-attach re-links the shadow node on thaw, but the stale render-timetintColorprop remains on screen; the relink doesn't catch the accent upuseUniwind()returns the pre-flip theme foreveruseCSSVariable/useResolveClassNamesare correct after thaw (they re-sync in their effect bodies)Mechanism: react-freeze hides via Suspense; React tears down the hidden tree's layout effects and re-creates them on reveal without re-rendering. The three broken paths resolve during render and subscribe in a
useLayoutEffectwhose body only re-subscribes — a notification that fired while the effects were torn down is never replayed (hoc/withUniwind.native.tsx,hooks/useUniwind.ts; accents viagetAccentColorin render). The two healthy hooks heal precisely because their effect body re-syncs (useResolveClassNames.native.ts→recreate(),useCSSVariable.ts→updateValue()). The same gap drops a change landing between initial render and subscribe.Suggested fix: give the broken paths the same effect-body re-sync the healthy hooks have (guarded to no-op on first mount, mirroring
useCSSVariable'sisMountRef), or move the subscriptions touseSyncExternalStore, which rides the passive-effect path React keeps connected under a suspended Suspense boundary. For accents, additionally catch the value up when a shadow node re-links.This is the report Brentlok asked for in #466 ("If you have similar issue for oss version please create a separated issue with reproduction") — the hook sources are identical in OSS 1.11.0, so it applies there too.
Steps to Reproduce
useCSSVariableline and className styles are correct; the tinted image and theuseUniwindline keep the old theme indefinitely (further freeze/thaw cycles stay stale; only a remount, or another flip while visible, repairs them). AwithUniwind(AnyIcon)behaves like the tinted image.freezeOnBlur: true, flipping the theme from the other tab. In a real app the staleness looks intermittent because list recycling remounts some elements and not others (React Compiler element caching removes the parent-re-render repair, making it sticky).Snack or Repository Link (Optional)
No response
Uniwind version
uniwind-pro 1.6.0
React Native Version
0.85.3
Platforms
iOS, Android
Expo
Yes
Additional information 〰