Migrating to Reanimated 3: what actually changed
· 3 min read · Amrith Vengalath
- React Native
- Reanimated
- Animation
Reanimated 3 landed and, going by the major version bump, I expected a painful migration. It wasn't. If you're already on Reanimated 2 with worklets and the modern API, most of your code just keeps working. But "most" isn't "all," and there are a couple of things that will bite you if you bump the version blind, so here's what I ran into upgrading a production app.
The big one: the v1 API is gone
Reanimated 3 drops the old v1 API entirely. In v2 you could still run legacy v1 animation code through a compatibility layer. In v3 that layer is removed. If any corner of your app - or, more likely, a dependency - is still on v1-style animations, it breaks.
For our own code this was a non-issue; we'd been all-in on worklets since v2. The trap was a third-party library that hadn't updated. Before you upgrade, check your animation-related dependencies actually support Reanimated 3. I found one the hard way when the build started throwing at a component I hadn't touched in months.
React 18 and concurrent rendering
Reanimated 3 is built for React 18. If you're upgrading both around the same time (a lot of people were), the thing to understand is that concurrent features can render components more than you'd expect, and animation setup code that assumed a single render can misbehave. In practice this meant being careful that shared values and animated styles were set up in the right places and not recreated unnecessarily. It wasn't dramatic, but it's the kind of thing that produces a subtle "why does this flicker once" bug.
Layout animations got better
The part I was actually happy about: layout animations and the shared element transitions matured. Entering and exiting animations on components became more reliable, and you can do a lot of "this list item animates in when added" work declaratively:
import Animated, { FadeIn, FadeOut, Layout } from "react-native-reanimated";
function ListItem({ item }) {
return (
<Animated.View
entering={FadeIn}
exiting={FadeOut}
layout={Layout.springify()}
>
<Text>{item.title}</Text>
</Animated.View>
);
}When an item is added or removed, it animates, and the others slide into place with a spring. Previously I'd have hand-rolled this with shared values and measured heights. Getting it as a one-liner was a genuine quality-of-life jump.
The migration, step by step
What worked for me, on a real app, with no drama:
- Read the changelog and the migration notes first. Actually read them - the breaking changes are short and specific.
- Audit dependencies for Reanimated 3 support before touching anything. This is where the real risk is, not in your own code.
- Bump the version, rebuild the native projects (this is a library with native code - a JS reload won't cut it, you need a fresh pod install and Gradle build).
- Run the app and exercise every animated surface on a real device, both platforms. Simulators hide frame issues.
- Watch for v1 API usage flagged at build time and convert it.
Was it worth doing promptly?
For an app under active development, yes - staying current with Reanimated keeps you eligible for the rest of the ecosystem's upgrades, and the layout animation improvements paid for the effort on their own. If you were still on v1 patterns, the jump is bigger and you should budget for it. If you were already living in worklets, it's an afternoon, most of which is spent verifying dependencies rather than rewriting your own animations.