What's Changed
A patch release with no breaking changes. It fixes a regression from 3.8.0: while a Reanimated animation ran, theme changes on Android landed seconds late and iOS debug builds could abort. On web, withUnistyles styles reach Gesture Handler 3 components again, and Expo Router's server output (web.output: "server") now works.
🎨 Themes and styles
- While a Reanimated animation runs, a theme change no longer freezes the UI for several seconds on Android, and iOS debug builds no longer abort with
attempts < 1024inShadowTree::commit. Since 3.8.0, Unistyles clones its nodes inside the commit. Reanimated commits on every frame, so each retry found a newer root, cloned and laid out the whole tree again, and lost again. A retry now reuses the tree from the previous attempt and only takes the newest root's layout constraints, so the Android rotation fix from 3.8.0 (#1347) still applies. This affected 3.8.0 only (#1365, closes #1363)
🌐 Web
withUnistylesstyles now reach Gesture Handler 3 components (RectButton,BaseButton,BorderlessButton,RawButton,LegacyPressable, and RNGH'sScrollView,TextInput,SwitchandFlatList). RNGH 3 renders adisplay: contentsview first, and Unistyles styled that view instead of the component. Unistyles doesn't look for RNGH: its child rules now also match the only child of anydisplay: contentsview, without raising specificity. This works with both the Babel plugin and the Metro integration (#1367, closes #1366)
🖥️ Expo Router server output
- With Expo Router's
web.output: "server"(Expo SDK 58+), pages no longer return a 500 with "no theme has been selected yet". Expo Router loads a route before the+html.tsxthat imports your config, so StyleSheets ran beforeStyleSheet.configure. On the server, a StyleSheet function created before the config is now created the first time it's used (#1368) - In the browser, Unistyles now takes over the server's styles on its own.
+html.tsxonly renders on the server, so before, nothing calledhydrateServerUnistyles(#1368) <Display>,<Hide>anduseAnimatedVariantColorno longer usewindowordocumentwhile rendering on the server, which made React fall back to client rendering for those pages. On the server,<Display>and<Hide>content stays hidden until the browser renders it. The variant color switches from the server's placeholder to the real color after hydration (#1369)- The Expo Router guide has a new server rendering section. Render
useServerUnistylesafterchildrenin+html.tsx. Otherwise each page gets the previous request's styles. The section also coversSuspenseboundaries and values that differ between server and browser (#1368)
// +html.tsx
import '../unistyles' // <-- file that initializes Unistyles
import { useServerDocumentContext } from 'expo-router/html'
import { type PropsWithChildren } from 'react'
import { useServerUnistyles } from 'react-native-unistyles/server'
// Rendered after the page, so it collects the styles the page rendered
const UnistylesStyles = () => <>{useServerUnistyles({ includeRNWStyles: false })}</>
export default function Root({ children }: PropsWithChildren) {
const { htmlAttributes, bodyAttributes, headNodes, bodyNodes } = useServerDocumentContext()
return (
<html {...htmlAttributes}>
<head>{headNodes}</head>
<body {...bodyAttributes}>
{children}
<UnistylesStyles />
{bodyNodes}
</body>
</html>
)
}❤️ Contributors
Thanks to those whose PRs and reports shaped the fixes:
- @erickreutz (#1364), who found and traced the iOS debug abort during animations
- @simondaigre (#1312), who reported and investigated the Expo Router server output issues
- @DragomirAndrei19 and @csheppard for detailed bug reports
Full Changelog: v3.8.0...v3.8.1