diff --git a/Libraries/Animated/src/NativeAnimatedHelper.js b/Libraries/Animated/src/NativeAnimatedHelper.js
index acb994a9b7d980..41d0e52e60da26 100644
--- a/Libraries/Animated/src/NativeAnimatedHelper.js
+++ b/Libraries/Animated/src/NativeAnimatedHelper.js
@@ -116,6 +116,13 @@ const API = {
invariant(NativeAnimatedModule, 'Native animated module is not available');
NativeAnimatedModule.disconnectAnimatedNodeFromView(nodeTag, viewTag);
},
+ restoreDefaultValues: function(nodeTag: number): void {
+ invariant(NativeAnimatedModule, 'Native animated module is not available');
+ // Backwards compat with older native runtimes, can be removed later.
+ if (NativeAnimatedModule.restoreDefaultValues != null) {
+ NativeAnimatedModule.restoreDefaultValues(nodeTag);
+ }
+ },
dropAnimatedNode: function(tag: number): void {
invariant(NativeAnimatedModule, 'Native animated module is not available');
NativeAnimatedModule.dropAnimatedNode(tag);
diff --git a/Libraries/Animated/src/NativeAnimatedModule.js b/Libraries/Animated/src/NativeAnimatedModule.js
index 9c3ef2d9215643..aa9e1257d2ce66 100644
--- a/Libraries/Animated/src/NativeAnimatedModule.js
+++ b/Libraries/Animated/src/NativeAnimatedModule.js
@@ -45,6 +45,7 @@ export interface Spec extends TurboModule {
+extractAnimatedNodeOffset: (nodeTag: number) => void;
+connectAnimatedNodeToView: (nodeTag: number, viewTag: number) => void;
+disconnectAnimatedNodeFromView: (nodeTag: number, viewTag: number) => void;
+ +restoreDefaultValues: (nodeTag: number) => void;
+dropAnimatedNode: (tag: number) => void;
+addAnimatedEventToView: (
viewTag: number,
diff --git a/Libraries/Animated/src/__tests__/AnimatedNative-test.js b/Libraries/Animated/src/__tests__/AnimatedNative-test.js
index b6e34047f6cf93..c511638be6add8 100644
--- a/Libraries/Animated/src/__tests__/AnimatedNative-test.js
+++ b/Libraries/Animated/src/__tests__/AnimatedNative-test.js
@@ -46,6 +46,7 @@ describe('Native Animated', () => {
extractAnimatedNodeOffset: jest.fn(),
flattenAnimatedNodeOffset: jest.fn(),
removeAnimatedEventFromView: jest.fn(),
+ restoreDefaultValues: jest.fn(),
setAnimatedNodeOffset: jest.fn(),
setAnimatedNodeValue: jest.fn(),
startAnimatingNode: jest.fn(),
@@ -837,4 +838,25 @@ describe('Native Animated', () => {
expect(NativeAnimatedModule.stopAnimation).toBeCalledWith(animationId);
});
});
+
+ describe('Animated Components', () => {
+ it('Should restore default values on prop updates only', () => {
+ const opacity = new Animated.Value(0);
+ opacity.__makeNative();
+
+ const root = TestRenderer.create();
+ expect(NativeAnimatedModule.restoreDefaultValues).not.toHaveBeenCalled();
+
+ root.update();
+ expect(NativeAnimatedModule.restoreDefaultValues).toHaveBeenCalledWith(
+ expect.any(Number),
+ );
+
+ root.unmount();
+ // Make sure it doesn't get called on unmount.
+ expect(NativeAnimatedModule.restoreDefaultValues).toHaveBeenCalledTimes(
+ 1,
+ );
+ });
+ });
});
diff --git a/Libraries/Animated/src/createAnimatedComponent.js b/Libraries/Animated/src/createAnimatedComponent.js
index 4a47ce6203e790..d803781e158194 100644
--- a/Libraries/Animated/src/createAnimatedComponent.js
+++ b/Libraries/Animated/src/createAnimatedComponent.js
@@ -111,7 +111,10 @@ function createAnimatedComponent(
// This way the intermediate state isn't to go to 0 and trigger
// this expensive recursive detaching to then re-attach everything on
// the very next operation.
- oldPropsAnimated && oldPropsAnimated.__detach();
+ if (oldPropsAnimated) {
+ oldPropsAnimated.__restoreDefaultValues();
+ oldPropsAnimated.__detach();
+ }
}
_setComponentRef = setAndForwardRef({
diff --git a/Libraries/Animated/src/nodes/AnimatedProps.js b/Libraries/Animated/src/nodes/AnimatedProps.js
index b45073f8014cf2..be630133f1a471 100644
--- a/Libraries/Animated/src/nodes/AnimatedProps.js
+++ b/Libraries/Animated/src/nodes/AnimatedProps.js
@@ -147,6 +147,16 @@ class AnimatedProps extends AnimatedNode {
);
}
+ __restoreDefaultValues(): void {
+ // When using the native driver, view properties need to be restored to
+ // their default values manually since react no longer tracks them. This
+ // is needed to handle cases where a prop driven by native animated is removed
+ // after having been changed natively by an animation.
+ if (this.__isNative) {
+ NativeAnimatedHelper.API.restoreDefaultValues(this.__getNativeTag());
+ }
+ }
+
__getNativeConfig(): Object {
const propsConfig = {};
for (const propKey in this._props) {
diff --git a/Libraries/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec-generated.mm b/Libraries/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec-generated.mm
index 2460facf8a0605..36330dc0cf04d8 100644
--- a/Libraries/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec-generated.mm
+++ b/Libraries/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec-generated.mm
@@ -281,6 +281,10 @@ + (RCTManagedPointer *)JS_NativeAnimatedModule_EventMapping:(id)json
return static_cast(turboModule).invokeObjCMethod(rt, VoidKind, "disconnectAnimatedNodeFromView", @selector(disconnectAnimatedNodeFromView:viewTag:), args, count);
}
+ static facebook::jsi::Value __hostFunction_NativeAnimatedModuleSpecJSI_restoreDefaultValues(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
+ return static_cast(turboModule).invokeObjCMethod(rt, VoidKind, "restoreDefaultValues", @selector(restoreDefaultValues:), args, count);
+ }
+
static facebook::jsi::Value __hostFunction_NativeAnimatedModuleSpecJSI_dropAnimatedNode(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
return static_cast(turboModule).invokeObjCMethod(rt, VoidKind, "dropAnimatedNode", @selector(dropAnimatedNode:), args, count);
}
@@ -344,6 +348,9 @@ + (RCTManagedPointer *)JS_NativeAnimatedModule_EventMapping:(id)json
methodMap_["disconnectAnimatedNodeFromView"] = MethodMetadata {2, __hostFunction_NativeAnimatedModuleSpecJSI_disconnectAnimatedNodeFromView};
+ methodMap_["restoreDefaultValues"] = MethodMetadata {1, __hostFunction_NativeAnimatedModuleSpecJSI_restoreDefaultValues};
+
+
methodMap_["dropAnimatedNode"] = MethodMetadata {1, __hostFunction_NativeAnimatedModuleSpecJSI_dropAnimatedNode};
diff --git a/Libraries/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec.h b/Libraries/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec.h
index 51fe41d78de983..1af255238dd867 100644
--- a/Libraries/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec.h
+++ b/Libraries/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec.h
@@ -290,6 +290,7 @@ namespace JS {
viewTag:(double)viewTag;
- (void)disconnectAnimatedNodeFromView:(double)nodeTag
viewTag:(double)viewTag;
+- (void)restoreDefaultValues:(double)nodeTag;
- (void)dropAnimatedNode:(double)tag;
- (void)addAnimatedEventToView:(double)viewTag
eventName:(NSString *)eventName
diff --git a/Libraries/NativeAnimation/RCTNativeAnimatedModule.mm b/Libraries/NativeAnimation/RCTNativeAnimatedModule.mm
index 6b7332c243ab71..ee4beec587d88a 100644
--- a/Libraries/NativeAnimation/RCTNativeAnimatedModule.mm
+++ b/Libraries/NativeAnimation/RCTNativeAnimatedModule.mm
@@ -160,14 +160,18 @@ - (void)setBridge:(RCTBridge *)bridge
RCT_EXPORT_METHOD(disconnectAnimatedNodeFromView:(double)nodeTag
viewTag:(double)viewTag)
{
- [self addPreOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
- [nodesManager restoreDefaultValues:[NSNumber numberWithDouble:nodeTag]];
- }];
[self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
[nodesManager disconnectAnimatedNodeFromView:[NSNumber numberWithDouble:nodeTag] viewTag:[NSNumber numberWithDouble:viewTag]];
}];
}
+RCT_EXPORT_METHOD(restoreDefaultValues:(double)nodeTag)
+{
+ [self addPreOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
+ [nodesManager restoreDefaultValues:[NSNumber numberWithDouble:nodeTag]];
+ }];
+}
+
RCT_EXPORT_METHOD(dropAnimatedNode:(double)tag)
{
[self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
diff --git a/ReactAndroid/src/main/java/com/facebook/fbreact/specs/NativeAnimatedModuleSpec.java b/ReactAndroid/src/main/java/com/facebook/fbreact/specs/NativeAnimatedModuleSpec.java
index 9ca4222c09a9c3..91710dbc5ebe0b 100644
--- a/ReactAndroid/src/main/java/com/facebook/fbreact/specs/NativeAnimatedModuleSpec.java
+++ b/ReactAndroid/src/main/java/com/facebook/fbreact/specs/NativeAnimatedModuleSpec.java
@@ -59,6 +59,9 @@ public abstract void removeAnimatedEventFromView(double viewTag, String eventNam
@ReactMethod
public abstract void setAnimatedNodeOffset(double nodeTag, double offset);
+ @ReactMethod
+ public abstract void restoreDefaultValues(double nodeTag);
+
@ReactMethod
public abstract void startAnimatingNode(double animationId, double nodeTag, ReadableMap config,
Callback endCallback);
diff --git a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java
index 7bd186202a6681..538f7672789326 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java
@@ -373,18 +373,22 @@ public void execute(NativeAnimatedNodesManager animatedNodesManager) {
@ReactMethod
public void disconnectAnimatedNodeFromView(final int animatedNodeTag, final int viewTag) {
- mPreOperations.add(
+ mOperations.add(
new UIThreadOperation() {
@Override
public void execute(NativeAnimatedNodesManager animatedNodesManager) {
- animatedNodesManager.restoreDefaultValues(animatedNodeTag, viewTag);
+ animatedNodesManager.disconnectAnimatedNodeFromView(animatedNodeTag, viewTag);
}
});
- mOperations.add(
+ }
+
+ @ReactMethod
+ public void restoreDefaultValues(final int animatedNodeTag) {
+ mPreOperations.add(
new UIThreadOperation() {
@Override
public void execute(NativeAnimatedNodesManager animatedNodesManager) {
- animatedNodesManager.disconnectAnimatedNodeFromView(animatedNodeTag, viewTag);
+ animatedNodesManager.restoreDefaultValues(animatedNodeTag);
}
});
}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java
index cff5d610d07725..9ce9e66470ccfb 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java
@@ -318,7 +318,7 @@ public void disconnectAnimatedNodeFromView(int animatedNodeTag, int viewTag) {
propsAnimatedNode.disconnectFromView(viewTag);
}
- public void restoreDefaultValues(int animatedNodeTag, int viewTag) {
+ public void restoreDefaultValues(int animatedNodeTag) {
AnimatedNode node = mAnimatedNodes.get(animatedNodeTag);
// Restoring default values needs to happen before UIManager operations so it is
// possible the node hasn't been created yet if it is being connected and
diff --git a/ReactAndroid/src/test/java/com/facebook/react/animated/NativeAnimatedNodeTraversalTest.java b/ReactAndroid/src/test/java/com/facebook/react/animated/NativeAnimatedNodeTraversalTest.java
index 9ce85cac0d9820..0a854969495375 100644
--- a/ReactAndroid/src/test/java/com/facebook/react/animated/NativeAnimatedNodeTraversalTest.java
+++ b/ReactAndroid/src/test/java/com/facebook/react/animated/NativeAnimatedNodeTraversalTest.java
@@ -981,7 +981,7 @@ public void testRestoreDefaultProps() {
assertThat(stylesCaptor.getValue().getDouble("opacity")).isEqualTo(0);
reset(mUIManagerMock);
- mNativeAnimatedNodesManager.restoreDefaultValues(propsNodeTag, viewTag);
+ mNativeAnimatedNodesManager.restoreDefaultValues(propsNodeTag);
verify(mUIManagerMock).synchronouslyUpdateViewOnUIThread(eq(viewTag), stylesCaptor.capture());
assertThat(stylesCaptor.getValue().isNull("opacity"));
}