diff --git a/apps/common-app/src/examples/RuntimeTests/RuntimeTestsExample.tsx b/apps/common-app/src/examples/RuntimeTests/RuntimeTestsExample.tsx
index 6a460dbf4ef..4ad23f2af2d 100644
--- a/apps/common-app/src/examples/RuntimeTests/RuntimeTestsExample.tsx
+++ b/apps/common-app/src/examples/RuntimeTests/RuntimeTestsExample.tsx
@@ -51,6 +51,8 @@ export default function RuntimeTestsExample() {
require('./tests/core/useDerivedValue/chain.test');
require('./tests/core/useSharedValue/animationsCompilerApi.test');
+
+ require('./tests/core/onLayout.test');
},
},
{
diff --git a/apps/common-app/src/examples/RuntimeTests/tests/core/onLayout.test.tsx b/apps/common-app/src/examples/RuntimeTests/tests/core/onLayout.test.tsx
new file mode 100644
index 00000000000..7d74fecaf55
--- /dev/null
+++ b/apps/common-app/src/examples/RuntimeTests/tests/core/onLayout.test.tsx
@@ -0,0 +1,69 @@
+import { useEffect, useRef } from 'react';
+import type { LayoutChangeEvent } from 'react-native';
+import { StyleSheet, View } from 'react-native';
+import Animated, { runOnJS, runOnUI, useAnimatedStyle, useEvent, useSharedValue } from 'react-native-reanimated';
+import { describe, expect, render, test, wait } from '../../ReJest/RuntimeTestsApi';
+
+interface TestResult {
+ height: number,
+ animatedHandlerCalled: boolean
+}
+
+const TestComponent = ({result}: {result: TestResult}) => {
+ const sv = useSharedValue(styles.smallBox.height);
+
+ const onLayout = (event: LayoutChangeEvent) => {
+ result.height = event.nativeEvent.layout.height;
+ };
+
+ const animatedStyle = useAnimatedStyle(() => {
+ return { height: sv.value };
+ });
+
+ useEffect(() => {
+ runOnUI(() => {
+ sv.value += 100;
+ })();
+ });
+
+ const setAnimatedHandlerCalled = () => {
+ result.animatedHandlerCalled = true;
+ };
+
+ const animatedOnLayout = useEvent(() => {
+ 'worklet';
+ runOnJS(setAnimatedHandlerCalled)();
+ }, ['onLayout']);
+
+ return (
+
+
+
+ );
+};
+
+describe('onLayout', () => {
+ test('is not intercepted when there are no registered event handlers', async () => {
+ const result = {} as TestResult;
+ await render();
+ await wait(500);
+ const { height } = result;
+ expect(height).toBe(200);
+ });
+
+ test('is dispatched to the registered event handler', async () => {
+ const result = {} as TestResult;
+ await render();
+ await wait(500);
+ const { animatedHandlerCalled } = result;
+ expect(animatedHandlerCalled).toBe(true);
+ });
+});
+
+const styles = StyleSheet.create({
+ smallBox: {
+ width: 100,
+ height: 100,
+ backgroundColor: 'pink',
+ },
+});
diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/NativeReanimatedModule.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/NativeReanimatedModule.cpp
index 404ca6b6493..a6782f6f793 100644
--- a/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/NativeReanimatedModule.cpp
+++ b/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/NativeReanimatedModule.cpp
@@ -613,6 +613,11 @@ bool NativeReanimatedModule::handleRawEvent(
if (eventType.rfind("top", 0) == 0) {
eventType = "on" + eventType.substr(3);
}
+
+ if (!isAnyHandlerWaitingForEvent(eventType, tag)) {
+ return false;
+ }
+
jsi::Runtime &rt = uiWorkletRuntime_->getJSIRuntime();
const auto &eventPayload = rawEvent.eventPayload;
jsi::Value payload = eventPayload->asJSIValue(rt);