react-native-reanimated icon indicating copy to clipboard operation
react-native-reanimated copied to clipboard

CSS Transitions hijacks ShadowTree updates for attached nodes

Open jpudysz opened this issue 2 months ago • 7 comments

Description

Hey!

A new issue came up and was reported by a Unistyles user. When both a CSS Transition and a Unistyles style are attached to the same component, theme changes don’t apply to those nodes.

I debugged it, my ShadowTree update sends the correct values, but it looks like CSS Transition nodes are handled differently on your side, which blocks me from applying any updates on top.

That’s not the case for regular Animated components using useAnimatedStyle. Could you let me know why this happens and more importantly how I can push an update for those nodes?

At first I thought it was because you spread the styles, but after checking your filterNonCSSStyleProps function, I realized that was a wrong assumption.

Steps to reproduce

  1. Open the app
Image
  1. Tap on "Check box" button
  2. One button will change color immediately and second one will slowly transition
Image
  1. Tap on "Change theme" button
  2. One button won't take in count new color applied by Unistyles (from ShadowTree)
Image Image

Shadow tree styles pushed to ShadowTree by Unistyles are correct:

Image

Each section represent one ShadowNode and styles that should be applied.

Original issue: https://github.com/jpudysz/react-native-unistyles/issues/1007

Snack or a link to a repository

https://github.com/believer/unistyles-repro

Reanimated version

4.1.3

Worklets version

0.6.1

React Native version

0.81.4

Platforms

Android, iOS

JavaScript runtime

Hermes

Workflow

React Native CLI

Architecture

New Architecture (Fabric renderer)

Build type

Debug app & dev bundle

Device

iOS simulator

Host machine

macOS

Device model

iPhone 16e

Acknowledgements

Yes

jpudysz avatar Oct 31 '25 14:10 jpudysz

Hi @jpudysz, thanks for reporting this issue.

Telling from the repro you have provided it looks like you're trying to set backgroundColor style both using Reanimated and Unistyles. Currently, Reanimated uses an approach that wasn't really designed to support style updates from multiple sources at once. This is however something we'd like to address at some point in the future.

Given that no one else has reported this problem so far and the fact that it only occurs if you mix Reanimated and Unistyles styling in the very same component we decided to treat this issue as low priority for now. However, feel free to investigate the codebase on your own and submit a pull request which we'll be more than happy to review if you manage to find a reliable solution.

tomekzaw avatar Nov 03 '25 14:11 tomekzaw

Can we make Reanimated skip overriding certain commits, for example by checking a commit trait? I’ll soon release Uniwind, which will do something similar, so I expect these issues to start piling up.

Right now, Unistyles uses deprecated React Native Animated escape hatches, which prevents Reanimated from overriding it, but we should probably find a cleaner solution.

I understand why you override commits due to the nature of animations, but sometimes users just want to change the theme. In such cases, the only options are to re-render or duplicate the theming logic in Reanimated hooks.

I can draft a PR with a potential fix if that helps.

jpudysz avatar Nov 03 '25 17:11 jpudysz

We would rather avoid adding any third-party library-specific escape hatches since they tend to pile up and make the logic overly complex. Such workarounds get more and more difficult to maintain and reason about in the long term.

If the bug happens only for CSS Transitions and doesn't happen for useAnimatedStyle animations, perhaps the difference lies in how the updates are propagated to the animated props and CSS transitions registries? This is the place I would investigate first.

tomekzaw avatar Nov 04 '25 09:11 tomekzaw

Hey @jpudysz!

I'd like to share more details on how CSS Transitions are implemented in Reanimated and why you encounter this issue.

When someone marks a certain property as the transitionProperty, then Reanimated takes the full control over this prop's value. The style passed to the Animated component is used as the source of truth. It is sent from JS to CPP where we calculate a diff and, if properties listed under the transitionProperty change, the CSS transition is triggered for them. Because of this design, we always override the value of transitioned properties in the commit hook, so values from other commits can't override these values.

We obtained this design by purpose, to prevent any changed from RN or other sources from breaking the CSS transition state.

As @tomekzaw already said, I think we won't allow commits from other sources to override these values as it will break the transition state.

MatiPl01 avatar Nov 04 '25 15:11 MatiPl01

Hey @MatiPl01, thanks for the insights. Sorry for the late reply I was OOO.

It seems there’s no other solution, and users need to re-render the app to pass new colors to Reanimated components that use CSS transitions.

I think it would be good to come up with an API or solution to address these issues. The same applies to overriding any other transitions via the ShadowTree produced by Unistyles or Uniwind. We need a next-level integration, as more and more developers will be using these libraries together with REA.

Right now, Reanimated acts like a sentry and assumes that only React Native can push directly to the ShadowTree.

jpudysz avatar Nov 12 '25 13:11 jpudysz

Reanimated acts like a sentry and assumes that only React Native can push directly to the ShadowTree.

Only in the case of CSS Transitions. As @MatiPl01 stated, this was designed on purpose, to prevent any changes from RN or other sources from breaking the CSS transition state.

tomekzaw avatar Nov 12 '25 14:11 tomekzaw

I understand it's about CSS Transitions, but that's only partially true. Without the nativeProps_DEPRECATED hack, updates are sometimes replaced by other animations as well.

Reanimated was originally designed with the assumption that only React Native pushes updates to the ShadowTree. That’s no longer the case, as more libraries now interact with it, eg: https://github.com/nativewind/react-native-css-nitro

Switching themes during runtime or ongoing animations is a realistic scenario. Publishing an official guide or API for handling this would be highly appreciated, otherwise, related issues will keep piling up.

This also means I can’t implement CSS Transitions directly inside Unistyles StyleSheet, since those changes never reach the ShadowTree. The only workaround is to re-render the view using the CSS Transition to propagate the new color down to C++.

My point is simply to think ahead. This will become more common over time.

jpudysz avatar Nov 12 '25 19:11 jpudysz