diff --git a/apps/common-app/src/examples/InvalidValueAccessExample.tsx b/apps/common-app/src/examples/InvalidValueAccessExample.tsx
new file mode 100644
index 00000000000..ff9c5dad966
--- /dev/null
+++ b/apps/common-app/src/examples/InvalidValueAccessExample.tsx
@@ -0,0 +1,89 @@
+import { Text, StyleSheet, View, Button } from 'react-native';
+
+import React, { useEffect } from 'react';
+import Animated, {
+ configureReanimatedLogger,
+ useAnimatedStyle,
+ useSharedValue,
+ withTiming,
+} from 'react-native-reanimated';
+
+configureReanimatedLogger({
+ // change to `false` or remove the `configureReanimatedLogger` call to
+ // disable the warning
+ strict: true,
+});
+
+export default function InvalidValueAccessExample() {
+ const [counter, setCounter] = React.useState(0);
+ const [updateFromUseEffect, setUpdateFromUseEffect] = React.useState(false);
+ const sv = useSharedValue(0);
+
+ if (!updateFromUseEffect) {
+ // logs writing to `value`... warning
+ sv.value = counter;
+ // logs reading from `value`... warning
+ console.log('shared value:', sv.value);
+ }
+
+ useEffect(() => {
+ if (updateFromUseEffect) {
+ // no warning is logged
+ sv.value = counter;
+ // no warning is logged
+ console.log('useEffect shared value:', sv.value);
+ }
+ }, [sv, counter, updateFromUseEffect]);
+
+ const reRender = () => {
+ setCounter((prev) => prev + 1);
+ };
+
+ const animatedBarStyle = useAnimatedStyle(() => ({
+ transform: [{ scaleX: withTiming(Math.sin((sv.value * Math.PI) / 10)) }],
+ }));
+
+ return (
+
+
+
+ Update from:{' '}
+
+ {updateFromUseEffect ? 'useEffect' : 'component'}
+
+
+
+
+ Counter: {counter}
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ bar: {
+ height: 20,
+ backgroundColor: 'blue',
+ width: '100%',
+ },
+ text: {
+ fontSize: 16,
+ },
+ highlight: {
+ fontWeight: 'bold',
+ },
+ row: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ gap: 10,
+ },
+});
diff --git a/apps/common-app/src/examples/index.ts b/apps/common-app/src/examples/index.ts
index 8cbdeeb5fe9..581058cc57e 100644
--- a/apps/common-app/src/examples/index.ts
+++ b/apps/common-app/src/examples/index.ts
@@ -133,6 +133,7 @@ import TabNavigatorExample from './SharedElementTransitions/TabNavigatorExample'
import StrictDOMExample from './StrictDOMExample';
import BottomTabsExample from './LayoutAnimations/BottomTabs';
import ListItemLayoutAnimation from './LayoutAnimations/ListItemLayoutAnimation';
+import InvalidValueAccessExample from './InvalidValueAccessExample';
interface Example {
icon?: string;
@@ -183,6 +184,11 @@ export const EXAMPLES: Record = {
title: 'Freezing shareables',
screen: FreezingShareablesExample,
},
+ InvalidReadWriteExample: {
+ icon: '🔒',
+ title: 'Invalid read/write during render',
+ screen: InvalidValueAccessExample,
+ },
// About
diff --git a/packages/react-native-reanimated/src/mutables.ts b/packages/react-native-reanimated/src/mutables.ts
index 79261a1014e..95b646ab5ce 100644
--- a/packages/react-native-reanimated/src/mutables.ts
+++ b/packages/react-native-reanimated/src/mutables.ts
@@ -2,7 +2,8 @@
import { shouldBeUseWeb } from './PlatformChecker';
import type { Mutable } from './commonTypes';
import { ReanimatedError } from './errors';
-
+import { logger } from './logger';
+import { isFirstReactRender, isReactRendering } from './reactUtils';
import { shareableMappingCache } from './shareableMappingCache';
import { makeShareableCloneRecursive } from './shareables';
import { executeOnUIRuntimeSync, runOnUI } from './threads';
@@ -10,6 +11,28 @@ import { valueSetter } from './valueSetter';
const SHOULD_BE_USE_WEB = shouldBeUseWeb();
+function shouldWarnAboutAccessDuringRender() {
+ return __DEV__ && isReactRendering() && !isFirstReactRender();
+}
+
+function checkInvalidReadDuringRender() {
+ if (shouldWarnAboutAccessDuringRender()) {
+ logger.warn(
+ 'Reading from `value` during component render. Please ensure that you do not access the `value` property while React is rendering a component.',
+ { strict: true }
+ );
+ }
+}
+
+function checkInvalidWriteDuringRender() {
+ if (shouldWarnAboutAccessDuringRender()) {
+ logger.warn(
+ 'Writing to `value` during component render. Please ensure that you do not access the `value` property while React is rendering a component.',
+ { strict: true }
+ );
+ }
+}
+
type Listener = (newValue: Value) => void;
/**
@@ -93,12 +116,14 @@ function makeMutableNative(initial: Value): Mutable {
const mutable: Mutable = {
get value(): Value {
+ checkInvalidReadDuringRender();
const uiValueGetter = executeOnUIRuntimeSync((sv: Mutable) => {
return sv.value;
});
return uiValueGetter(mutable);
},
set value(newValue) {
+ checkInvalidWriteDuringRender();
runOnUI(() => {
mutable.value = newValue;
})();
@@ -146,9 +171,11 @@ function makeMutableWeb(initial: Value): Mutable {
const mutable: Mutable = {
get value(): Value {
+ checkInvalidReadDuringRender();
return value;
},
set value(newValue) {
+ checkInvalidWriteDuringRender();
valueSetter(mutable, newValue);
},
diff --git a/packages/react-native-reanimated/src/reactUtils.ts b/packages/react-native-reanimated/src/reactUtils.ts
new file mode 100644
index 00000000000..a2ad79f0c0d
--- /dev/null
+++ b/packages/react-native-reanimated/src/reactUtils.ts
@@ -0,0 +1,22 @@
+'use strict';
+import React from 'react';
+
+function getCurrentReactOwner() {
+ const ReactSharedInternals =
+ // @ts-expect-error React secret internals aren't typed
+ React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED ||
+ // @ts-expect-error React secret internals aren't typed
+ React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;
+ return ReactSharedInternals?.ReactCurrentOwner?.current;
+}
+
+export function isReactRendering() {
+ return !!getCurrentReactOwner();
+}
+
+export function isFirstReactRender() {
+ const currentOwner = getCurrentReactOwner();
+ // alternate is not null only after the first render and stores all the
+ // data from the previous component render
+ return currentOwner && !currentOwner?.alternate;
+}