What happened?
Built-in Uniwind components can retain stale className styles after their layout effects reconnect, until an unrelated update causes them to render again.
For example, a memoized tab starts in light mode, is hidden with React Activity, the theme changes to dark, and the tab is shown again. Its internal state is preserved, but its text and panel can still use the light colors. Accent colors resolved through the same hook are affected too.
Expected: restored content keeps its state and reflects the current theme.
Actual: restored content keeps its state but can display the previous theme indefinitely. A subsequent theme change while visible repairs it.
Steps to Reproduce
- Install the pinned dependencies and launch the standalone Expo app.
- Start in light mode and increment the saved counter.
- Press Hide tab, Toggle theme, then Show tab.
- The runtime theme reads dark, but the memoized card retains the light palette. The saved counter is retained.
- Repeat with a system appearance change while following system theme.
The reproduction uses React, React Native, Expo, and OSS Uniwind. It does not require navigation, HeroUI, a backend, React Compiler, or Uniwind Pro. Explicit memoization reproduces the stable-child boundary that a compiler can also produce.
Source-level cause
packages/uniwind/src/components/native/useStyle.ts computes styles during render. Its layout effect only subscribes to subsequent changes. React may reconnect that effect without rendering unchanged children, so changes that happened during disconnection are never reconciled.
Capturing the dependency revision at render and checking it immediately after subscribing closes this gap. The candidate adds seven lines and uses the existing listener revision counter. It does not keep hidden component subscriptions active or remount tab content.
Snack or Repository Link
https://github.com/eliotgevers/uniwind-activity-reproduction
Uniwind version
1.12.0
React Native Version
0.88.0-rc.2 (iOS simulator reproduction); 0.86.0 (native Jest regressions)
Platforms
iOS
Expo
Yes
Environment and verification
- Uniwind: 1.12.0; current main c7978fd has the same affected hook.
- React Native regression tests: React 19.2.3 / RN 0.86.0.
- Lifecycle matrix: React DOM 19.2.3 and 19.3.0 with the real native style store/listener and stubbed RN environment constants. This supports lifecycle attribution, not native rendering claims.
- iOS 27.0 simulator: Expo 58.0.0-preview.4 / React 19.3.0 / RN 0.88.0-rc.2. Stock fails both hidden explicit theme directions, OS light-to-dark while hidden, and a hidden variable update; candidate passes the same sequence and visible/display:none controls.
- No Android device claim is made.
Proposed fix with two focused native regressions: #690. No public API changes.
Additional information 〰
What happened?
Built-in Uniwind components can retain stale
classNamestyles after their layout effects reconnect, until an unrelated update causes them to render again.For example, a memoized tab starts in light mode, is hidden with React
Activity, the theme changes to dark, and the tab is shown again. Its internal state is preserved, but its text and panel can still use the light colors. Accent colors resolved through the same hook are affected too.Expected: restored content keeps its state and reflects the current theme.
Actual: restored content keeps its state but can display the previous theme indefinitely. A subsequent theme change while visible repairs it.
Steps to Reproduce
The reproduction uses React, React Native, Expo, and OSS Uniwind. It does not require navigation, HeroUI, a backend, React Compiler, or Uniwind Pro. Explicit memoization reproduces the stable-child boundary that a compiler can also produce.
Source-level cause
packages/uniwind/src/components/native/useStyle.tscomputes styles during render. Its layout effect only subscribes to subsequent changes. React may reconnect that effect without rendering unchanged children, so changes that happened during disconnection are never reconciled.Capturing the dependency revision at render and checking it immediately after subscribing closes this gap. The candidate adds seven lines and uses the existing listener revision counter. It does not keep hidden component subscriptions active or remount tab content.
Snack or Repository Link
https://github.com/eliotgevers/uniwind-activity-reproduction
Uniwind version
1.12.0
React Native Version
0.88.0-rc.2 (iOS simulator reproduction); 0.86.0 (native Jest regressions)
Platforms
iOS
Expo
Yes
Environment and verification
Proposed fix with two focused native regressions: #690. No public API changes.
Additional information 〰
useStylebehavior with stable children and includes an independent reproduction.