From 1a3c91f07eb03cf5c1c0cd24eba1ea46656b56c3 Mon Sep 17 00:00:00 2001 From: ErfanBagheri404 Date: Wed, 9 Sep 2026 11:19:29 +0330 Subject: [PATCH] fix: skip and cache color computations in TouchableRipple and Chip (#4946) --- src/components/Chip/helpers.tsx | 15 ++++++++++++++- .../TouchableRipple/TouchableRipple.tsx | 13 ++++++++++--- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/components/Chip/helpers.tsx b/src/components/Chip/helpers.tsx index 3091d36e31..95ebfbe7e0 100644 --- a/src/components/Chip/helpers.tsx +++ b/src/components/Chip/helpers.tsx @@ -9,6 +9,19 @@ const md3 = (theme: InternalTheme) => theme; const stateOpacity = tokens.md.sys.state.opacity; +// `color(selectedColor).alpha(0.29).rgb().string()` is pure, so the result is +// cached per input color to avoid re-parsing on every Chip render (#4946). +const selectedColorBorderCache = new Map(); + +const getSelectedColorBorder = (selectedColor: string): string => { + let border = selectedColorBorderCache.get(selectedColor); + if (border === undefined) { + border = color(selectedColor).alpha(0.29).rgb().string(); + selectedColorBorderCache.set(selectedColor, border); + } + return border; +}; + export type ChipAvatarProps = { style?: StyleProp; }; @@ -39,7 +52,7 @@ const getBorderColor = ({ if (isSelectedColor) { if (typeof selectedColor === 'string') { - return color(selectedColor).alpha(0.29).rgb().string(); + return getSelectedColorBorder(selectedColor); } // PlatformColor / OpaqueColorValue: skip the alpha pass and render opaque. return selectedColor; diff --git a/src/components/TouchableRipple/TouchableRipple.tsx b/src/components/TouchableRipple/TouchableRipple.tsx index 128e2c017e..2f5a7d7ab6 100644 --- a/src/components/TouchableRipple/TouchableRipple.tsx +++ b/src/components/TouchableRipple/TouchableRipple.tsx @@ -119,12 +119,19 @@ const TouchableRipple = ({ theme, rippleColor, }); + const isWeb = Platform.OS === 'web'; // Web-only style. PlatformColor doesn't exist on web, so the calculated - // ripple color is effectively always a string here. - const hoverColor = - typeof calculatedRippleColor === 'string' + // ripple color is effectively always a string here. The `color()` chain is + // pure, so it is memoized and skipped entirely on native platforms where + // `hoverColor` is never applied. + const hoverColor = React.useMemo(() => { + if (!isWeb) { + return calculatedRippleColor; + } + return typeof calculatedRippleColor === 'string' ? color(calculatedRippleColor).fade(0.5).rgb().string() : calculatedRippleColor; + }, [calculatedRippleColor, isWeb]); const { rippleEffectEnabled } = React.useContext(SettingsContext); const { onPress, onLongPress, onPressIn, onPressOut } = rest;