Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[3.16.1] NativeWind classes not being applied on Animated.View #6665

Closed
amandaharlin opened this issue Nov 4, 2024 · 15 comments · Fixed by #6760
Closed

[3.16.1] NativeWind classes not being applied on Animated.View #6665

amandaharlin opened this issue Nov 4, 2024 · 15 comments · Fixed by #6760
Labels
Platform: Web This issue is specific to web Repro provided A reproduction with a snippet of code, snack or repo is provided

Comments

@amandaharlin
Copy link

amandaharlin commented Nov 4, 2024

Description

React-Native-Reanimated (at 3.10.1) was working with NativeWind to correctly apply classes to an Animated.View.

After upgrading Reanimated to 3.16.1 NW classnames were no longer getting applied to an Animated.View.

I opened a ticket with NativeWind too, so both teams could be notified.

Working Project Details Not-Working Project Details
Expo 52.0.0-preview.18 Expo 52.0.0-preview.18
React NativeReanimated 3.10.1 React NativeReanimated at 3.16.1
NativeWind at 4.1.21 NativeWind at 4.1.21
rnreanimated 3.10.1 rnreanimated 3.16.1
Screenshot 2024-11-04 at 10 18 46 AM Screenshot 2024-11-04 at 10 20 16 AM

Steps to reproduce

  1. Create a repo with npx create-expo-stack@latest --nativewind
  2. Upgrade Reanimated from 3.10.1 to 3.16.1
  3. Add classNames to an animated view. (Should work, but does not)
  4. Downgrade Reanimated to 3.10.1. (Works as expected)

Snack or a link to a repository

https://github.com/amandaharlin/react-native-reanimated-nativewind-classname-repro

Reanimated version

3.16.1

React Native version

0.76.1

Platforms

Web

JavaScript runtime

None

Workflow

Expo Go

Architecture

None

Build type

Debug app & dev bundle

Device

None

Device model

No response

Acknowledgements

Yes

@github-actions github-actions bot added Repro provided A reproduction with a snippet of code, snack or repo is provided Platform: Web This issue is specific to web labels Nov 4, 2024
@marklawlor
Copy link
Contributor

marklawlor commented Nov 4, 2024

I have no investigated this yet. But incase this is a reanimated issue, NativeWind on web works by passing a StyleQ precompiled styled object to the <View />.

<View style={{ $$css: true, myUniqueIdentifier: 'my-class' }} /> 

will output as 

<div class="my-class" />

@amandaharlin
Copy link
Author

Hey y'all, after checking 3.12, 3.14, and 3.15.5, I found that the change appeared in 3.16.0.

@marklawlor
Copy link
Contributor

This issue is caused by how react-native-reanimated builds in 3.1.16.

NativeWind relies on a jsx transform (turning <View> -> jsx(View), where it needs to use the jsx function from NativeWind.

3.1.16 is being published to NPM with the jsx function being hardcoded to react/jsx-runtime. This also means that react-native-reanimated will not work with libraries like @emotion/native which relies on the jsx transform.

I'm not actually sure why this is happeneding, as the tsconfig.json#jsx is correctly set to react-native, which should preserve the JSX.

A work around is to simply not use Animated.View. You can just use a View and add an animation class to it like animate-none, and NativeWind will turn your View into an Animated.View

@amandaharlin
Copy link
Author

amandaharlin commented Nov 6, 2024

@marklawlor Thank you for checking this out and sharing that alternative. That is useful in many situations, but there's some situations where we needed lower level control to do something more precisely. These are instances where tailwind isn't driving the animation, we're just adding additional tailwind classes and the javascript calculations are driving the animation (e.g. moving a box around in absolute space, relying on screen measurements, but also needs some tailwind classes, like flex, typography, or a background color, applied to an element; classes unrelated to that animation).

What we're trying for now in those places is we're implementing the first workaround where you're using the object to set style (with a ts-ignore). In this situation the class is applied correctly to the animated view, instead of using the more convenient classname property. In situations where it looks like the animation view can be swapped for a view and transpiled to a view, we're doing that too. Thanks again for your OSS work, both of y'all, and all your help.

@marklawlor
Copy link
Contributor

Another workaround is to use

const MyAnimatedView = cssInterop(Animated.View, { className: "style" })

<MyAnimatedView />

@amandaharlin
Copy link
Author

amandaharlin commented Nov 8, 2024

Thanks! That works great. Here's my full cross-platform component in typescript.

animated-view.web.tsx

import Animated from 'react-native-reanimated';
import type { ViewProps } from 'react-native';
import type { AnimatedProps } from 'react-native-reanimated';
import { cssInterop } from 'nativewind';
import { cn } from '~/lib/utils';

const InteropAnimatedView = cssInterop(Animated.View, { className: 'style' }); 

export const AnimatedView = ({ className, ...animatedViewProps }: AnimatedProps<ViewProps>) => {
  return <InteropAnimatedView {...animatedViewProps} className={cn(className)} />;
};

animated-view.tsx

import Animated from 'react-native-reanimated';

export const AnimatedView = Animated.View;

utils.tsx

import type { ClassValue } from 'clsx';
import { clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

implementation.tsx

import Animated, { useAnimatedStyle } from 'react-native-reanimated';
import { AnimatedView } from '~/components/ui/animated-view';

const viewStyle = useAnimatedStyle(() => { 
/// interpolate here
}) 

<AnimatedView style={viewStyle} className="flex items-center justify-center">
  <View/>
</AnimatedView>

I'm using this with expo. It was working correctly on iOS and Android, this code snippet implements the change for the web build. With the .web switch, this puts them at feature parity.

Would you like for this to be the official way to use RNReanimated and NW? I can make a PR to the NW docs, which would close this issue.

@marklawlor
Copy link
Contributor

cssInterop returns a new component. Calling it during render will always cause a remount of that component (as your rendering something different everytime)

@amandaharlin
Copy link
Author

amandaharlin commented Nov 12, 2024

I wanted to provide a quick update here, firstly

import Animated from 'react-native-reanimated';
import type { ViewProps } from 'react-native';
import type { AnimatedProps } from 'react-native-reanimated';
import { cssInterop } from 'nativewind';
import { cn } from '~/lib/utils';

//move to here
const InteropAnimatedView = cssInterop(Animated.View, { className: 'style' }); 

export const AnimatedView = ({ className, ...animatedViewProps }: AnimatedProps<ViewProps>) => {
  //const InteropAnimatedView = cssInterop(Animated.View, { className: 'style' }); 
  return <InteropAnimatedView {...animatedViewProps} className={cn(className)} />;
};

this works just fine after testing, and should avoid the performance pitfall you mention, so I've updated my code snippet

That said, i've not yet updated my reproduction repo, but the mobile behavior has actually degraded more from the time I posted this issue. I had been using

import Animated from 'react-native-reanimated';
export const AnimatedView = Animated.View;

and this was working beforehand on the mobile platforms. Now, when I use that code or if I use the css Interop approach for mobile, neither one is working. While trying to zero in on "what changed", I can see that I haven't changed nativewind or react-native-reanimated but expo did update. I can't see immediately how that could cause this issue, so I am still investigating. That said, last week iOS and Android were working (and only web needed to be shimmed), now mobile doesn't work at all, but the fix upthread for web does still work great for web builds, and seems to have no effect on mobile.

To get around this I've fallen back to just specifying css properties with the standard JSON syntax on the style prop for now.

@marklawlor
Copy link
Contributor

This issue is an upstream bug with react-native-builder-bob callstack/react-native-builder-bob#678

I've the issue isn't addressed soon then I'll probably adjust the nativewind babel plugin to work around it

@kaumac
Copy link

kaumac commented Nov 19, 2024

I'm having the same problem, I was using Moti (which uses Reanimated under the hood) and classNames were working fine.
After upgrading Reanimated the classNames stopped working. I then tried using just raw Reanimated and it was the same problem (worked before 3.16.1 and stopped working after that).

For now I've been using just plain style={{}}

I don't fully understand from the conversation if we'll be able to just use classNames on the animated View directly or if creating a custom cssInterop wrapper is the way to go moving forward?

@tjzel
Copy link
Collaborator

tjzel commented Nov 20, 2024

@marklawlor @amandaharlin Thanks for troubleshooting this!

@focux
Copy link

focux commented Nov 20, 2024

Is there a workaround that we can apply meanwhile? I have a big codebase with many animated views and would be a pain to change all those views to use a style object until this is fixed.

@marklawlor would patching the react-native-builder-bob babel-preset fix the issue?

@tjzel
Copy link
Collaborator

tjzel commented Nov 21, 2024

@marklawlor I submitted a pull request in react-native-builder-bob to address this issue. We'll release 3.16.3 with these changes. However, you should somehow migrate from relying on classic jsx runtime as automatic is going to be the default soon.

github-merge-queue bot pushed a commit to callstack/react-native-builder-bob that referenced this issue Nov 21, 2024
<!-- Please provide enough information so that others can review your
pull request. -->
<!-- Keep pull requests small and focused on a single change. -->

### Summary

In
0595213
the JSX runtime was changed to `automatic` without the option for the
user to opt back in for `classic`.

The `classic` runtime is needed for NativeWind library, which is
dependent on Reanimated.

Fixes
-
software-mansion/react-native-reanimated#6665
- #678 

### Test plan

🚀

---------

Co-authored-by: Satyajit Sahoo <satyajit.happy@gmail.com>
@marklawlor
Copy link
Contributor

marklawlor commented Nov 21, 2024

@tjzel I believe you have misunderstood this issue

NativeWind prefers the JSX runtime to be set to automatic. When the runtime is automatic additional JSX configuration options are available. jsxImportSource allows you to specify where the jsx function used by the automatic runtime is located.

The TypeScript JSX option is different to setting the JSX runtime option, it changes how TypeScript compiles to JS, which can involve setting the runtime but also has other options. The recommend option provided by @tsconfig/react-native is preserve which should output JSX runtime agnostic output

import React from 'react';
export const HelloWorld = () => <h1>Hello world</h1>; // The code is preserved. No runtime has been selected

You can see here during the NativeWind installation that we require the jsxImportSource to be set to nativewind. This means when Babel transforms JSX (which should be left as preserve) will compile it to the automatic runtime with the jsx import coming from NativeWind, not React. The issue is that the Reanimated JSX is being precompiled with jsxImportSource set to react, so when a user compiles their local project no jsxImportSource transformation is being applied for the Reanimated files.

Looking at your PR, the actual fix needs to

If `tsconfig.json#jsx === 'preserve' || tsconfig.json#jsx === 'react-native'`
  Then disable  `@babel/plugin-transform-react-jsx`

Interestingly, this PR will fix people's problems as NativeWind does also support classic. But we would prefer if people used automatic

@tjzel
Copy link
Collaborator

tjzel commented Nov 22, 2024

@marklawlor You are right, I got confused when you were talking about TypeScript's jsx option. This is because when I analyzed the code of react-native-builder-bob it seems that it has never used the project's tsconfig.json for anything, except for generating type definitions. The incompatibility between Reanimated and NativeWind happened, because the runtime was switched from classic to automatic.

I don't think disabling @babel/plugin-transform-react-jsx or @babel/preset-react is a feasible solution for us now. We have always depended on the way react-native-builder-bob prepares emitted esm files. Disabling those plugins/presets could break our other users workflows.

github-merge-queue bot pushed a commit that referenced this issue Nov 25, 2024
<!-- Thanks for submitting a pull request! We appreciate you spending
the time to work on these changes. Please follow the template so that
the reviewers can easily understand what the code changes affect. -->

## Summary

Fixes
#6665

Applying the change I made to `react-native-builder-bob`:
- callstack/react-native-builder-bob#695

It restores `classic` runtime for the `@babel/react-native` preset used
for generating ESModule code. The behavior was changed when we bumped
builder-bob:
- #6485


## Test plan

I ran the `diff` for the version of `lib` without `jsxRuntime: classic`
and with it - the differences are only in regards to `_jsx` usage. You
can read more here:
-
#6665
tjzel added a commit that referenced this issue Nov 25, 2024
<!-- Thanks for submitting a pull request! We appreciate you spending
the time to work on these changes. Please follow the template so that
the reviewers can easily understand what the code changes affect. -->

Fixes
#6665

Applying the change I made to `react-native-builder-bob`:
- callstack/react-native-builder-bob#695

It restores `classic` runtime for the `@babel/react-native` preset used
for generating ESModule code. The behavior was changed when we bumped
builder-bob:
- #6485

I ran the `diff` for the version of `lib` without `jsxRuntime: classic`
and with it - the differences are only in regards to `_jsx` usage. You
can read more here:
-
#6665
tjzel added a commit that referenced this issue Nov 26, 2024
<!-- Thanks for submitting a pull request! We appreciate you spending
the time to work on these changes. Please follow the template so that
the reviewers can easily understand what the code changes affect. -->

Fixes
#6665

Applying the change I made to `react-native-builder-bob`:
- callstack/react-native-builder-bob#695

It restores `classic` runtime for the `@babel/react-native` preset used
for generating ESModule code. The behavior was changed when we bumped
builder-bob:
- #6485

I ran the `diff` for the version of `lib` without `jsxRuntime: classic`
and with it - the differences are only in regards to `_jsx` usage. You
can read more here:
-
#6665
tjzel added a commit that referenced this issue Dec 13, 2024
<!-- Thanks for submitting a pull request! We appreciate you spending
the time to work on these changes. Please follow the template so that
the reviewers can easily understand what the code changes affect. -->

## Summary

Fixes
#6665

Applying the change I made to `react-native-builder-bob`:
- callstack/react-native-builder-bob#695

It restores `classic` runtime for the `@babel/react-native` preset used
for generating ESModule code. The behavior was changed when we bumped
builder-bob:
- #6485


## Test plan

I ran the `diff` for the version of `lib` without `jsxRuntime: classic`
and with it - the differences are only in regards to `_jsx` usage. You
can read more here:
-
#6665
tjzel added a commit that referenced this issue Dec 13, 2024
<!-- Thanks for submitting a pull request! We appreciate you spending
the time to work on these changes. Please follow the template so that
the reviewers can easily understand what the code changes affect. -->

## Summary

Fixes
#6665

Applying the change I made to `react-native-builder-bob`:
- callstack/react-native-builder-bob#695

It restores `classic` runtime for the `@babel/react-native` preset used
for generating ESModule code. The behavior was changed when we bumped
builder-bob:
- #6485


## Test plan

I ran the `diff` for the version of `lib` without `jsxRuntime: classic`
and with it - the differences are only in regards to `_jsx` usage. You
can read more here:
-
#6665
tjzel added a commit that referenced this issue Dec 13, 2024
<!-- Thanks for submitting a pull request! We appreciate you spending
the time to work on these changes. Please follow the template so that
the reviewers can easily understand what the code changes affect. -->

## Summary

Fixes
#6665

Applying the change I made to `react-native-builder-bob`:
- callstack/react-native-builder-bob#695

It restores `classic` runtime for the `@babel/react-native` preset used
for generating ESModule code. The behavior was changed when we bumped
builder-bob:
- #6485


## Test plan

I ran the `diff` for the version of `lib` without `jsxRuntime: classic`
and with it - the differences are only in regards to `_jsx` usage. You
can read more here:
-
#6665
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Platform: Web This issue is specific to web Repro provided A reproduction with a snippet of code, snack or repo is provided
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants