Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add setNativeProps helper function on UI runtime #4595

Merged
merged 13 commits into from
Aug 7, 2023
70 changes: 70 additions & 0 deletions app/src/examples/SetNativePropsExample.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import Animated, {
runOnJS,
setNativeProps,
useAnimatedRef,
} from 'react-native-reanimated';
import { Button, StyleSheet } from 'react-native';
import {
Gesture,
GestureDetector,
GestureHandlerRootView,
TextInput,
} from 'react-native-gesture-handler';

import React from 'react';

const AnimatedTextInput = Animated.createAnimatedComponent(TextInput);

function delay(ms: number) {
const start = performance.now();
while (performance.now() - start < ms) {}
}

export default function SetNativePropsExample() {
const [text, setText] = React.useState('Hello');

const animatedRef = useAnimatedRef<TextInput>();

const send = () => {
delay(500);
console.log('SEND', text);
setText(''); // calling setText affects the JS state but doesn't update the native view
};

const tap = Gesture.Tap().onEnd(() => {
'worklet';
setNativeProps(animatedRef, {
text: '',
backgroundColor: `hsl(${Math.random() * 360}, 100%, 50%)`,
});
runOnJS(send)();
});

return (
<GestureHandlerRootView style={styles.container}>
<AnimatedTextInput
value={text}
onChangeText={setText}
style={styles.input}
ref={animatedRef}
autoFocus
/>
<GestureDetector gesture={tap}>
<Button title="Send" />
</GestureDetector>
</GestureHandlerRootView>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
input: {
width: 250,
padding: 3,
borderWidth: 1,
},
});
20 changes: 13 additions & 7 deletions app/src/examples/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import AnimatedTabBarExample from './AnimatedTabBarExample';
import AnimatedTextInputExample from './AnimatedTextInputExample';
import AnimatedTextWidthExample from './AnimatedTextWidthExample';
import ArticleProgressExample from './ArticleProgressExample';
import BabelVersionCheckExample from './BabelVersionCheckExample';
import BasicLayoutAnimation from './LayoutAnimations/BasicLayoutAnimation';
import BasicNestedAnimation from './LayoutAnimations/BasicNestedAnimation';
import BasicNestedLayoutAnimation from './LayoutAnimations/BasicNestedLayoutAnimation';
Expand All @@ -29,6 +30,7 @@ import DefaultAnimations from './LayoutAnimations/DefaultAnimations';
import DeleteAncestorOfExiting from './LayoutAnimations/DeleteAncestorOfExiting';
import DispatchCommandExample from './DispatchCommandExample';
import DragAndSnapExample from './DragAndSnapExample';
import DuplicateTagsExample from './SharedElementTransitions/DuplicateTags';
import EmojiWaterfallExample from './EmojiWaterfallExample';
import EmptyExample from './EmptyExample';
import ExtrapolationExample from './ExtrapolationExample';
Expand All @@ -46,6 +48,7 @@ import LightBoxExample from './LightBoxExample';
import LiquidSwipe from './LiquidSwipe/LiquidSwipe';
import ManyScreensExample from './SharedElementTransitions/ManyScreens';
import ManyTagsExample from './SharedElementTransitions/ManyTags';
import MatrixTransform from './MatrixTransform';
import MeasureExample from './MeasureExample';
import Modal from './LayoutAnimations/Modal';
import ModalNewAPI from './LayoutAnimations/ModalNewAPI';
Expand All @@ -54,7 +57,6 @@ import MountingUnmounting from './LayoutAnimations/MountingUnmounting';
import NativeModals from './LayoutAnimations/NativeModals';
import NestedNativeStacksWithLayout from './LayoutAnimations/NestedNativeStacksWithLayout';
import NestedStacksExample from './SharedElementTransitions/NestedStacks';
import ProgressTransitionExample from './SharedElementTransitions/ProgressTransition';
import NestedTest from './LayoutAnimations/Nested';
import NewestShadowNodesRegistryRemoveExample from './NewestShadowNodesRegistryRemoveExample';
import NonLayoutPropAndRenderExample from './NonLayoutPropAndRenderExample';
Expand All @@ -63,7 +65,10 @@ import OldMeasureExample from './OldMeasureExample';
import OlympicAnimation from './LayoutAnimations/OlympicAnimation';
import OverlappingBoxesExample from './OverlappingBoxesExample';
import PagerExample from './CustomHandler/PagerExample';
import PendulumExample from './PendulumExample';
import PinExample from './PinExample';
import ProfilesExample from './SharedElementTransitions/Profiles';
import ProgressTransitionExample from './SharedElementTransitions/ProgressTransition';
import RainbowExample from './RainbowExample';
import ReactionsCounterExample from './LayoutAnimations/ReactionsCounterExample';
import ReducedMotionExample from './ReducedMotionExample';
Expand All @@ -76,24 +81,20 @@ import ScrollToExample from './ScrollToExample';
import ScrollViewExample from './ScrollViewExample';
import ScrollViewOffsetExample from './ScrollViewOffsetExample';
import ScrollableViewExample from './ScrollableViewExample';
import SetNativePropsExample from './SetNativePropsExample';
import SharedStyleExample from './SharedStyleExample';
import SpringLayoutAnimation from './LayoutAnimations/SpringLayoutAnimation';
import SvgExample from './SvgExample';
import SwipeableList from './LayoutAnimations/SwipeableList';
import SwipeableListExample from './SwipeableListExample';
import TransformExample from './TransformExample';
import UpdatePropsPerfExample from './UpdatePropsPerfExample';
import VolumeExample from './VolumeExample';
import WaterfallGridExample from './LayoutAnimations/WaterfallGridExample';
import WidthExample from './WidthExample';
import WithoutBabelPluginExample from './WithoutBabelPluginExample';
import WobbleExample from './WobbleExample';
import WorkletExample from './WorkletExample';
import ProfilesExample from './SharedElementTransitions/Profiles';
import VolumeExample from './VolumeExample';
import MatrixTransform from './MatrixTransform';
import PendulumExample from './PendulumExample';
import DuplicateTagsExample from './SharedElementTransitions/DuplicateTags';
import BabelVersionCheckExample from './BabelVersionCheckExample';

interface Example {
icon?: string;
Expand Down Expand Up @@ -162,6 +163,11 @@ export const EXAMPLES: Record<string, Example> = {
title: 'Letters',
screen: LettersExample,
},
SetNativePropsExample: {
icon: '🪄',
title: 'setNativeProps',
screen: SetNativePropsExample,
},
UpdatePropsPerfExample: {
icon: '🏎️',
title: 'Update props performance',
Expand Down
31 changes: 31 additions & 0 deletions src/reanimated2/Colors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

/* eslint no-bitwise: 0 */
import { StyleProps } from './commonTypes';
import { makeShareable } from './core';
import { isAndroid, isWeb } from './PlatformChecker';

Expand Down Expand Up @@ -274,6 +275,27 @@ const names: any = makeShareable({
yellowgreen: 0x9acd32ff,
});

// copied from react-native/Libraries/Components/View/ReactNativeStyleAttributes
export const ColorProperties = makeShareable([
'backgroundColor',
'borderBottomColor',
'borderColor',
'borderLeftColor',
'borderRightColor',
'borderTopColor',
'borderStartColor',
'borderEndColor',
'borderBlockColor',
'borderBlockEndColor',
'borderBlockStartColor',
'color',
'shadowColor',
'textDecorationColor',
'tintColor',
'textShadowColor',
'overlayColor',
]);

function normalizeColor(color: unknown): number | null {
'worklet';

Expand Down Expand Up @@ -597,6 +619,15 @@ export function processColor(color: unknown): number | null | undefined {
return normalizedColor;
}

export function processColorsInProps(props: StyleProps) {
'worklet';
for (const key in props) {
if (ColorProperties.includes(key)) {
props[key] = processColor(props[key]);
}
}
}

export type ParsedColorArray = [number, number, number, number];

export function convertToRGBA(color: unknown): ParsedColorArray {
Expand Down
2 changes: 1 addition & 1 deletion src/reanimated2/NativeMethods.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { MeasuredDimensions, ShadowNodeWrapper } from './commonTypes';
import { MeasuredDimensions, ShadowNodeWrapper } from './commonTypes';
import {
isChromeDebugger,
isJest,
Expand Down
59 changes: 59 additions & 0 deletions src/reanimated2/SetNativeProps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { ShadowNodeWrapper, StyleProps } from './commonTypes';
import {
isChromeDebugger,
isJest,
isWeb,
shouldBeUseWeb,
} from './PlatformChecker';

import type { AnimatedRef } from './hook/commonTypes';
import type { Component } from 'react';
import { _updatePropsJS } from './js-reanimated';
import { processColorsInProps } from './Colors';

const IS_NATIVE = !shouldBeUseWeb();

export let setNativeProps: <T extends Component>(
animatedRef: AnimatedRef<T>,
updates: StyleProps
) => void;

if (isWeb()) {
setNativeProps = (_animatedRef, _updates) => {
const component = (_animatedRef as any)();
_updatePropsJS(_updates, { _component: component });
};
} else if (IS_NATIVE && global._IS_FABRIC) {
setNativeProps = (animatedRef, updates) => {
'worklet';
const shadowNodeWrapper = (animatedRef as any)() as ShadowNodeWrapper;
processColorsInProps(updates);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
_updatePropsFabric!([{ shadowNodeWrapper, updates }]);
};
} else if (IS_NATIVE) {
setNativeProps = (animatedRef, updates) => {
'worklet';
const tag = (animatedRef as any)() as number;
const name = (animatedRef as any).viewName.value;
processColorsInProps(updates);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
_updatePropsPaper!([{ tag, name, updates }]);
};
} else if (isChromeDebugger()) {
setNativeProps = () => {
console.warn(
'[Reanimated] setNativeProps() is not supported with Chrome Debugger.'
);
};
} else if (isJest()) {
setNativeProps = () => {
console.warn('[Reanimated] setNativeProps() is not supported with Jest.');
};
} else {
setNativeProps = () => {
console.warn(
'[Reanimated] setNativeProps() is not supported on this configuration.'
);
};
}
29 changes: 2 additions & 27 deletions src/reanimated2/UpdateProps.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,13 @@
import type { MutableRefObject } from 'react';
import { processColor } from './Colors';
import { processColorsInProps } from './Colors';
import type { ShadowNodeWrapper, SharedValue, StyleProps } from './commonTypes';
import type { AnimatedStyle } from './helperTypes';
import { makeShareable } from './core';
import type { Descriptor } from './hook/commonTypes';
import { _updatePropsJS } from './js-reanimated';
import { shouldBeUseWeb } from './PlatformChecker';
import type { ViewRefSet } from './ViewDescriptorsSet';
import { runOnUIImmediately } from './threads';

// copied from react-native/Libraries/Components/View/ReactNativeStyleAttributes
const colorProps = [
'backgroundColor',
'borderBottomColor',
'borderColor',
'borderLeftColor',
'borderRightColor',
'borderTopColor',
'borderStartColor',
'borderEndColor',
'color',
'shadowColor',
'textDecorationColor',
'tintColor',
'textShadowColor',
'overlayColor',
];

export const ColorProperties = makeShareable(colorProps);

let updateProps: (
viewDescriptor: SharedValue<Descriptor[]>,
updates: StyleProps | AnimatedStyle<any>,
Expand All @@ -47,11 +26,7 @@ if (shouldBeUseWeb()) {
} else {
updateProps = (viewDescriptors, updates) => {
'worklet';
for (const key in updates) {
if (ColorProperties.indexOf(key) !== -1) {
updates[key] = processColor(updates[key]);
}
}
processColorsInProps(updates);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
global.UpdatePropsManager!.update(viewDescriptors, updates);
};
Expand Down
3 changes: 1 addition & 2 deletions src/reanimated2/animation/styleAnimation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ import type {
import type { AnimatedStyle } from '../helperTypes';
import type { StyleLayoutAnimation } from './commonTypes';
import { withTiming } from './timing';
import { ColorProperties } from '../UpdateProps';
import { processColor } from '../Colors';
import { ColorProperties, processColor } from '../Colors';

// resolves path to value for nested objects
// if path cannot be resolved returns undefined
Expand Down
16 changes: 14 additions & 2 deletions src/reanimated2/hook/useAnimatedRef.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,16 @@ import {
makeShareableCloneRecursive,
registerShareableMapping,
} from '../shareables';
import { findNodeHandle } from 'react-native';
import { Platform, findNodeHandle } from 'react-native';

interface MaybeScrollableComponent extends Component {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
getNativeScrollRef?: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
getScrollableNode?: any;
viewConfig?: {
uiViewClassName?: string;
};
}

function getComponentOrScrollable(component: MaybeScrollableComponent) {
Expand All @@ -33,6 +37,8 @@ export function useAnimatedRef<
T extends MaybeScrollableComponent
>(): AnimatedRef<T> {
const tag = useSharedValue<number | ShadowNodeWrapper | null>(-1);
const viewName = useSharedValue<string | null>(null);

const ref = useRef<AnimatedRef<T>>();

if (!ref.current) {
Expand All @@ -41,6 +47,10 @@ export function useAnimatedRef<
if (component) {
tag.value = getTagValueFunction(getComponentOrScrollable(component));
fun.current = component;
// viewName is required only on iOS with Paper
if (Platform.OS === 'ios' && !global._IS_FABRIC) {
viewName.value = component?.viewConfig?.uiViewClassName || 'RCTView';
}
}
return tag.value;
});
Expand All @@ -50,7 +60,9 @@ export function useAnimatedRef<
const remoteRef = makeShareableCloneRecursive({
__init: () => {
'worklet';
return () => tag.value;
const f = () => tag.value;
f.viewName = viewName;
return f;
},
});
registerShareableMapping(fun, remoteRef);
Expand Down
1 change: 1 addition & 0 deletions src/reanimated2/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export * from './interpolation';
export * from './interpolateColor';
export * from './Easing';
export * from './NativeMethods';
export { setNativeProps } from './SetNativeProps';
export * from './Colors';
export * from './PropAdapters';
export * from './layoutReanimation';
Expand Down