---
title: "Bridging Animated.Value and SharedValue in React Native"
description: "Animated.Value and Reanimated’s SharedValue don’t interoperate, making it hard to mix components across libraries. This article explains why and shows how to connect them natively without relying on the JavaScript thread."
url: https://www.callstack.com/blog/bridging-animated-value-and-sharedvalue-in-react-native
updated: 2026-10-24T08:39:27.937Z
---

# Bridging Animated.Value and SharedValue in React Native

Animated.Value and Reanimated’s SharedValue don’t interoperate, making it hard to mix components across libraries. This article explains why and shows how to connect them natively without relying on the JavaScript thread.

_By Satyajit Sahoo, Software Engineer @ Callstack · Published 2025-09-23 · Category: React Native_

Both `Animated.Value` ([from React Native's Animated API](https://reactnative.dev/docs/animated)) and `SharedValue` ([from Reanimated](https://docs.swmansion.com/react-native-reanimated)) are containers for values that can change without triggering re-renders, and are typically used to drive animations. But since they are from different libraries, there is no interoperability between them.

If you install a third-party component that uses Animated API, and you use Reanimated in your app, you can’t use your Reanimated value to drive animations in the component using Animated and vice versa.

Both Animated and Reanimated have a way to listen to changes in  `Animated.Value` and `SharedValue`, respectively, so you could subscribe to changes in one and update the other by using `animatedValue.setValue` or `sharedValue.set` respectively. However, this isn’t ideal, as the listeners run in JavaScript thread. So if the JavaScript thread is busy, the values won’t update in time, and animations won’t be smooth.

To solve this challenge, I decided to create a new library - [`react-native-animated-observer`](https://github.com/satya164/react-native-animated-observer) - to address this issue by allowing you to convert between these formats natively.

## How it works

Both Animated API and Reanimated have ways to drive animations from the UI thread. This makes it possible to perform smooth animations based on scroll, gestures, etc. It works by attaching the Animated or Reanimated values to such events. Animated provides the [`Animated.event`](https://reactnative.dev/docs/animated#handling-gestures-and-other-events) API, and Reanimated provides the [`useEvent`](https://docs.swmansion.com/react-native-reanimated/docs/advanced/useEvent/) API to handle this.

This library uses the same concept:

- It renders a native component that receives an `Animated.Node` or `SharedValue`. When the value changes, the library dispatches an event with this value.
- Then an Animated or Reanimated value is attached to this event to update it without going through the JavaScript thread.

Here is some pseudo-code to demonstrate conversion between a Reanimated `SharedValue` and an `Animated.Value`:

```reasonml
<SomeComponent
  // The component receives a reanimated shared value in a prop
  value={reanimatedSharedValue}
  // The value change is dispatched in `onValueChange` event
  onValueChange={Animated.event(
    // The value from e.nativeEvent.value is used to update animatedValue
    [{ nativeEvent: { value: animatedValue } }]
  )}
 />
```

## Challenges

While the concept is simple, dispatching an event from a Fabric UI component that works with Animated and Reanimated APIs is quite tricky. So let’s go through how to make it work.

### Codegen

First, if we’re using [Codegen](https://reactnative.dev/docs/the-new-architecture/what-is-codegen), we need to define the type of the event. It can be `DirectEventHandler` or `BubblingEventHandler`.

In our case, we want to use `DirectEventHandler`:

```typescript
type ValueChangeEvent = {
  value: CodegenTypes.Double;
};

interface NativeProps extends ViewProps {
  // other props

  onValueChange?: CodegenTypes.DirectEventHandler<ValueChangeEvent>;
}
```

### Android

To dispatch an event from a UI component on Android, first, we need to define a `getExportedCustomDirectEventTypeConstants`method in our view manager:

```kotlin
class MyViewManager : SimpleViewManager<MyView>(),
  MyViewManagerInterface<MyView> {
  
  // ...
  
  override fun getExportedCustomDirectEventTypeConstants() = mapOf(
    "onValueChange" to mapOf("registrationName" to "onValueChange")
  }
}
```

Next, define a class for our event:

```kotlin
inner class ValueChangeEvent(
  surfaceId: Int, viewId: Int, private val value: Double
) : Event<ValueChangeEvent>(surfaceId, viewId) {
  override fun getEventName() = "onValueChange"

  override fun getEventData(): WritableMap = Arguments.createMap().apply {
    putDouble("value", value)
  }
}
```

Then, we can dispatch the event using the id of the view that should receive the event:

```kotlin
val reactContext = context as ReactContext
val surfaceId = UIManagerHelper.getSurfaceId(reactContext)
val eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, surfaceId)
  ?: throw IllegalStateException("$NAME: EventDispatcher is not available for surfaceId: $surfaceId")

eventDispatcher.dispatchEvent(ValueChangeEvent(surfaceId, this.id, value))
```

Now we should receive events when we add an event listener on our component in JS.

#### Making it work with `useNativeDriver`

While this setup usually works for standard event handling, it fails when using `Animated.Event` with `useNativeDriver: true`. This is because Fabric expects all events to be fired with a `top` prefix. It [normalizes event names by adding `top` if the user hasn’t already](https://github.com/cortinico/react-native/blob/main/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleConstantsHelper.java#L136-L162) when using the interop layer.

However, in Fabric views that don’t use the interop layer, the native driver listens directly to the raw native event name and does not go through this normalization step, which means it only responds to events that are explicitly prefixed with `top`.

So we need to go back and replace `onValueChange` with `topValueChange`.

### iOS

Events can be dispatched on iOS in the following way:

```cpp
auto eventEmitter = std::static_pointer_cast<const MyViewEventEmitter>(self->_eventEmitter);

if (eventEmitter) {
  eventEmitter->onValueChange(MyViewEventEmitter::OnValueChange{
    .value = value
  });
}
```

That’s it! As you can see, the necessary code is already generated by codegen, meaning we don’t have to worry about the actual event names and normalization.

#### Making it work with `useNativeDriver`

Unfortunately, this still doesn’t work when using `useNativeDriver: true`. The reason is that the native driver listens for events directly on the native side. The only way to reach it is through a deprecated event-dispatcher API that, in current React Native, is only used internally by `ScrollView`.

To notify the Animated module yourself, you must explicitly fire this deprecated event.

> [There’s an open item in React Native](https://github.com/facebook/react-native/blob/44f431b471c243c92284aa042d3807ba4d04af65/packages/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm#L59-L62) to replace this legacy mechanism with a cleaner integration for native-driven animations, at which point this workaround will no longer be needed.

First, we need to define a class similar to Android. Let’s make a header file `RCTOnValueChangeEvent.h`:

```objectivec
@interface RCTOnValueChangeEvent : NSObject <RCTEvent>

- (instancetype) initWithReactTag:(NSNumber *)reactTag
                         value:(NSNumber *)value;

@end
```

Then the class `RCTOnValueChangeEvent.mm`:

```objectivec
@implementation RCTOnValueChangeEvent
{
    NSNumber* _value;
}

@synthesize viewTag = _viewTag;

- (NSString *)eventName {
    return @"onValueChange";
}

- (instancetype) initWithReactTag:(NSNumber *)reactTag
                         value:(NSNumber *)value
{
    RCTAssertParam(reactTag);
    
    if ((self = [super init])) {
        _viewTag = reactTag;
        _value = value;
    }
    return self;
}

RCT_NOT_IMPLEMENTED(- (instancetype)init)
- (uint16_t)coalescingKey
{
    return 0;
}


- (BOOL)canCoalesce
{
    return YES;
}

+ (NSString *)moduleDotMethod
{
    return @"RCTEventEmitter.receiveEvent";
}

- (NSArray *)arguments
{
    return @[self.viewTag, RCTNormalizeInputEventName(self.eventName), @{
                 @"value": _value
                 }];
}

- (id<RCTEvent>)coalesceWithEvent:(id<RCTEvent>)newEvent;
{
    return newEvent;
}

@end
```

Finally, we can use this class to dispatch the event:

```objectivec
ValueChangeEvent *event = [[ValueChangeEvent alloc] initWithReactTag:@(self.tag)
                                                               value:value];
NSDictionary *userInfo = @{@"event": event};
[[NSNotificationCenter defaultCenter] postNotificationName:@"RCTNotifyEventDispatcherObserversOfEvent_DEPRECATED"
                                                    object:nil
                                                  userInfo:userInfo];
```

Now our event will work with `Animated.event` with native driver.

## Wrapping up

As you can see, dispatching events from Fabric UI components is simple, unless you are dealing with legacy architecture or older APIs (for example, `useNativeDriver` which behaves quite differently in how and when it subscribes to native updates).

Now that you are aware of some of these gotchas, it should be easier if you need to dispatch events in your own component. Feel free to take a look at the source code of [https://github.com/satya164/react-native-animated-observer](https://github.com/satya164/react-native-animated-observer) if you are still having issues. It’s a small library, so hopefully it’ll be easier to understand.

And thanks to [@Piotr Trocki](https://www.callstack.com/team/piotr-trocki), [@Oskar Kwaśniewski](https://www.callstack.com/team/oskar-kwasniewski) and [@Mike Grabowski](https://www.callstack.com/team/mike-grabowski) for sharing gotchas around Fabric and events with me, otherwise I’d be lost as well.
