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..bb138eccc5a
--- /dev/null
+++ b/apps/common-app/src/examples/RuntimeTests/tests/core/onLayout.test.tsx
@@ -0,0 +1,71 @@
+import { useEffect } 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, notify, render, test, wait, waitForNotify } from '../../ReJest/RuntimeTestsApi';
+
+interface TestResult {
+ height: number;
+ animatedHandlerCalled: boolean;
+}
+
+const TestComponent = ({ result, notifyId }: { result: TestResult; notifyId: number }) => {
+ const height = useSharedValue(styles.smallBox.height);
+
+ const onLayout = (event: LayoutChangeEvent) => {
+ result.height = event.nativeEvent.layout.height;
+ if (result.height === 200) {
+ notify(`onLayout${notifyId}`);
+ }
+ };
+
+ const animatedStyle = useAnimatedStyle(() => {
+ return { height: height.value };
+ });
+
+ useEffect(() => {
+ runOnUI(() => {
+ height.value += 100;
+ })();
+ }, [height]);
+
+ const setAnimatedHandlerCalled = () => {
+ result.animatedHandlerCalled = true;
+ notify(`animatedOnLayout${notifyId}`);
+ };
+
+ 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 Promise.race([waitForNotify('onLayout1'), wait(1000)]);
+ expect(result.height).toBe(200);
+ });
+
+ test('is dispatched to the registered event handler', async () => {
+ const result = {} as TestResult;
+ await render();
+ await Promise.race([waitForNotify('animatedOnLayout2'), wait(1000)]);
+ expect(result.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/ReanimatedModuleProxy.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp
index b159b070d6c..39599f6e45e 100644
--- a/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp
+++ b/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp
@@ -553,6 +553,11 @@ bool ReanimatedModuleProxy::handleRawEvent(
if (eventType.rfind("top", 0) == 0) {
eventType = "on" + eventType.substr(3);
}
+
+ if (!isAnyHandlerWaitingForEvent(eventType, tag)) {
+ return false;
+ }
+
jsi::Runtime &rt =
workletsModuleProxy_->getUIWorkletRuntime()->getJSIRuntime();
const auto &eventPayload = rawEvent.eventPayload;