Skip to content

Commit 6994c7a

Browse files
authored
perf: Replace useSharedValue with useMutableValue (#408)
## Description This PR replaces all `useSharedValue` usages with `useMutableValue`. `useSharedValue` is heavier because of the cleanup `useEffect` that cancels currently running animation if there is any: ``` export function useSharedValue<Value>(initialValue: Value): SharedValue<Value> { const [mutable] = useState(() => makeMutable(initialValue)); useEffect(() => { return () => { cancelAnimation(mutable); }; }, [mutable]); return mutable; } ``` I don't need this behavior as I never use infinite animations and, in most cases, don't use animations at all, rather update value by myself. I only use animations for items reordering and the drop indicator, but since they are just short `withTiming` animations, the cleanup is not necessary. Even if the animation keeps running after the view was unmounted, it runs for a brief while, so there is no performance benefit in eager cleanup and it's better to reduce the number of unnecessary `useEffect` hooks.
1 parent 5066edd commit 6994c7a

21 files changed

Lines changed: 113 additions & 114 deletions

packages/react-native-sortables/src/components/shared/DraggableView/ActiveItemPortal.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ import { type PropsWithChildren, type ReactNode, useEffect } from 'react';
22
import {
33
runOnJS,
44
type SharedValue,
5-
useAnimatedReaction,
6-
useSharedValue
5+
useAnimatedReaction
76
} from 'react-native-reanimated';
87

98
import { usePortalContext } from '../../../providers';
9+
import { useMutableValue } from '../../../utils';
1010

1111
type ActiveItemPortalProps = PropsWithChildren<{
1212
teleportedItemId: string;
@@ -21,7 +21,7 @@ export default function ActiveItemPortal({
2121
teleportedItemId
2222
}: ActiveItemPortalProps) {
2323
const { teleport } = usePortalContext()!;
24-
const teleportEnabled = useSharedValue(false);
24+
const teleportEnabled = useMutableValue(false);
2525

2626
useEffect(() => {
2727
if (teleportEnabled.value) {

packages/react-native-sortables/src/components/shared/DraggableView/DraggableView.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ import { Fragment, memo, useEffect, useState } from 'react';
33
import { GestureDetector } from 'react-native-gesture-handler';
44
import {
55
LayoutAnimationConfig,
6-
useDerivedValue,
7-
useSharedValue
6+
useDerivedValue
87
} from 'react-native-reanimated';
98

109
import {
@@ -17,7 +16,7 @@ import {
1716
usePortalContext
1817
} from '../../../providers';
1918
import type { AnimatedStyleProp, LayoutAnimation } from '../../../types';
20-
import { getContextProvider } from '../../../utils';
19+
import { getContextProvider, useMutableValue } from '../../../utils';
2120
import ActiveItemPortal from './ActiveItemPortal';
2221
import ItemCell from './ItemCell';
2322
import TeleportedItemCell from './TeleportedItemCell';
@@ -47,7 +46,7 @@ function DraggableView({
4746
const teleportedItemId = `${componentId}-${key}`;
4847

4948
const [isTeleported, setIsTeleported] = useState(false);
50-
const activationAnimationProgress = useSharedValue(0);
49+
const activationAnimationProgress = useMutableValue(0);
5150
const isActive = useDerivedValue(() => activeItemKey.value === key);
5251
const itemStyles = useItemStyles(key, isActive, activationAnimationProgress);
5352
const gesture = useItemPanGesture(key, activationAnimationProgress);

packages/react-native-sortables/src/components/shared/DropIndicator.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import Animated, {
88
useAnimatedReaction,
99
useAnimatedStyle,
1010
useDerivedValue,
11-
useSharedValue,
1211
withTiming
1312
} from 'react-native-reanimated';
1413

@@ -18,6 +17,7 @@ import type {
1817
DropIndicatorComponentProps,
1918
Vector
2019
} from '../../types';
20+
import { useMutableValue } from '../../utils';
2121

2222
const DEFAULT_STYLE: ViewStyle = {
2323
opacity: 0
@@ -42,13 +42,13 @@ function DropIndicator({ DropIndicatorComponent, style }: DropIndicatorProps) {
4242
// Clone the array in order to prevent user from mutating the internal state
4343
const orderedItemKeys = useDerivedValue(() => [...indexToKey.value]);
4444

45-
const dropIndex = useSharedValue(0);
46-
const dropPosition = useSharedValue<Vector>({ x: 0, y: 0 });
47-
const prevUpdateItemKey = useSharedValue<null | string>(null);
48-
const dimensions = useSharedValue<Dimensions | null>(null);
45+
const dropIndex = useMutableValue(0);
46+
const dropPosition = useMutableValue<Vector>({ x: 0, y: 0 });
47+
const prevUpdateItemKey = useMutableValue<null | string>(null);
48+
const dimensions = useMutableValue<Dimensions | null>(null);
4949

50-
const x = useSharedValue<null | number>(null);
51-
const y = useSharedValue<null | number>(null);
50+
const x = useMutableValue<null | number>(null);
51+
const y = useMutableValue<null | number>(null);
5252

5353
useAnimatedReaction(
5454
() => ({

packages/react-native-sortables/src/hooks/reanimated/useStableCallbackValue.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
import { useCallback, useEffect } from 'react';
2-
import {
3-
isWorkletFunction,
4-
runOnJS,
5-
useSharedValue
6-
} from 'react-native-reanimated';
2+
import { isWorkletFunction, runOnJS } from 'react-native-reanimated';
73

84
import type { AnyFunction } from '../../types';
5+
import { useMutableValue } from '../../utils';
96

107
// We cannot store a function as a SharedValue because reanimated will treat
118
// it as an animation and will try to execute the animation when assigned
@@ -39,7 +36,7 @@ const wrap = <C extends AnyFunction>(callback: C): WrappedCallback<C> => {
3936
export default function useStableCallbackValue<C extends AnyFunction>(
4037
callback?: C
4138
) {
42-
const stableCallback = useSharedValue<null | WrappedCallback<C>>(null);
39+
const stableCallback = useMutableValue<null | WrappedCallback<C>>(null);
4340

4441
useEffect(() => {
4542
if (callback) {

packages/react-native-sortables/src/providers/layout/flex/FlexLayoutProvider.tsx

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
import { type PropsWithChildren, useCallback } from 'react';
22
import type { SharedValue } from 'react-native-reanimated';
3-
import {
4-
useAnimatedReaction,
5-
useDerivedValue,
6-
useSharedValue
7-
} from 'react-native-reanimated';
3+
import { useAnimatedReaction, useDerivedValue } from 'react-native-reanimated';
84

95
import { type DEFAULT_SORTABLE_FLEX_PROPS, IS_WEB } from '../../../constants';
106
import { useDebugContext } from '../../../debug';
@@ -14,7 +10,7 @@ import {
1410
type RequiredBy,
1511
type SortableFlexStyle
1612
} from '../../../types';
17-
import { haveEqualPropValues } from '../../../utils';
13+
import { haveEqualPropValues, useMutableValue } from '../../../utils';
1814
import { useCommonValuesContext, useMeasurementsContext } from '../../shared';
1915
import { createProvider } from '../../utils';
2016
import { calculateLayout, updateLayoutDebugRects } from './utils';
@@ -66,7 +62,7 @@ const { FlexLayoutProvider, useFlexLayoutContext } = createProvider(
6662
const { applyControlledContainerDimensions } = useMeasurementsContext();
6763
const debugContext = useDebugContext();
6864

69-
const keyToGroup = useSharedValue<Record<string, number>>({});
65+
const keyToGroup = useMutableValue<Record<string, number>>({});
7066

7167
const columnGap = useDerivedValue(() => columnGap_ ?? gap);
7268
const rowGap = useDerivedValue(() => rowGap_ ?? gap);
@@ -109,7 +105,7 @@ const { FlexLayoutProvider, useFlexLayoutContext } = createProvider(
109105
};
110106
});
111107

112-
const appliedLayout = useSharedValue<FlexLayout | null>(null);
108+
const appliedLayout = useMutableValue<FlexLayout | null>(null);
113109

114110
// Because the number of groups can dynamically change after order change
115111
// and we can't detect that in the React runtime, we are creating debug

packages/react-native-sortables/src/providers/layout/flex/updates/insert/index.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
import type { SharedValue } from 'react-native-reanimated';
2-
import {
3-
useAnimatedReaction,
4-
useDerivedValue,
5-
useSharedValue
6-
} from 'react-native-reanimated';
2+
import { useAnimatedReaction, useDerivedValue } from 'react-native-reanimated';
73

84
import type {
95
Coordinate,
@@ -15,7 +11,8 @@ import {
1511
error,
1612
gt as gt_,
1713
lt as lt_,
18-
reorderInsert
14+
reorderInsert,
15+
useMutableValue
1916
} from '../../../../../utils';
2017
import {
2118
getAdditionalSwapOffset,
@@ -80,10 +77,12 @@ const useInsertStrategy: SortableFlexStrategyFactory = ({
8077
crossGap = rowGap;
8178
}
8279

83-
const swappedBeforeIndexes = useSharedValue<ItemGroupSwapResult | null>(null);
84-
const swappedAfterIndexes = useSharedValue<ItemGroupSwapResult | null>(null);
85-
const swappedBeforeLayout = useSharedValue<FlexLayout | null>(null);
86-
const swappedAfterLayout = useSharedValue<FlexLayout | null>(null);
80+
const swappedBeforeIndexes = useMutableValue<ItemGroupSwapResult | null>(
81+
null
82+
);
83+
const swappedAfterIndexes = useMutableValue<ItemGroupSwapResult | null>(null);
84+
const swappedBeforeLayout = useMutableValue<FlexLayout | null>(null);
85+
const swappedAfterLayout = useMutableValue<FlexLayout | null>(null);
8786
const debugBox = useDebugBoundingBox();
8887

8988
const activeGroupIndex = useDerivedValue(() => {

packages/react-native-sortables/src/providers/layout/grid/GridLayoutProvider.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ import { type PropsWithChildren, useCallback } from 'react';
22
import {
33
type SharedValue,
44
useAnimatedReaction,
5-
useDerivedValue,
6-
useSharedValue
5+
useDerivedValue
76
} from 'react-native-reanimated';
87

98
import { IS_WEB } from '../../../constants';
@@ -14,6 +13,7 @@ import {
1413
type GridLayout,
1514
type GridLayoutContextType
1615
} from '../../../types';
16+
import { useMutableValue } from '../../../utils';
1717
import { useCommonValuesContext, useMeasurementsContext } from '../../shared';
1818
import { createProvider } from '../../utils';
1919
import { calculateLayout } from './utils';
@@ -69,7 +69,7 @@ const { GridLayoutProvider, useGridLayoutContext } = createProvider(
6969
* width - in vertical orientation (default) (columns are groups)
7070
* height - in horizontal orientation (rows are groups)
7171
*/
72-
const mainGroupSize = useSharedValue<null | number>(rowHeight ?? null);
72+
const mainGroupSize = useMutableValue<null | number>(rowHeight ?? null);
7373

7474
// MAIN GROUP SIZE UPDATER
7575
useAnimatedReaction(

packages/react-native-sortables/src/providers/layout/grid/updates/insert.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1-
import { useAnimatedReaction, useSharedValue } from 'react-native-reanimated';
1+
import { useAnimatedReaction } from 'react-native-reanimated';
22

33
import { EMPTY_ARRAY } from '../../../../constants';
4-
import { areArraysDifferent, reorderInsert } from '../../../../utils';
4+
import {
5+
areArraysDifferent,
6+
reorderInsert,
7+
useMutableValue
8+
} from '../../../../utils';
59
import {
610
useCommonValuesContext,
711
useCustomHandleContext
@@ -24,7 +28,7 @@ import { createGridStrategy } from './common';
2428
function useInactiveIndexToKey() {
2529
const { activeItemKey, indexToKey } = useCommonValuesContext();
2630
const { fixedItemKeys } = useCustomHandleContext() ?? {};
27-
const result = useSharedValue<Array<string>>(EMPTY_ARRAY);
31+
const result = useMutableValue<Array<string>>(EMPTY_ARRAY);
2832

2933
useAnimatedReaction(
3034
() => ({

packages/react-native-sortables/src/providers/layout/grid/updates/swap.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1-
import { useAnimatedReaction, useSharedValue } from 'react-native-reanimated';
1+
import { useAnimatedReaction } from 'react-native-reanimated';
22

33
import { EMPTY_ARRAY } from '../../../../constants';
4-
import { areArraysDifferent, reorderSwap } from '../../../../utils';
4+
import {
5+
areArraysDifferent,
6+
reorderSwap,
7+
useMutableValue
8+
} from '../../../../utils';
59
import { useCommonValuesContext } from '../../../shared';
610
import { useGridLayoutContext } from '../GridLayoutProvider';
711
import { getMainIndex } from '../utils';
@@ -26,7 +30,7 @@ import { createGridStrategy } from './common';
2630
function useInactiveIndexToKey() {
2731
const { activeItemKey, indexToKey, keyToIndex } = useCommonValuesContext();
2832
const { numGroups } = useGridLayoutContext();
29-
const result = useSharedValue<Array<string>>(EMPTY_ARRAY);
33+
const result = useMutableValue<Array<string>>(EMPTY_ARRAY);
3034

3135
useAnimatedReaction(
3236
() => ({

packages/react-native-sortables/src/providers/shared/ActiveItemValuesProvider.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import type { ReactNode } from 'react';
2-
import { useSharedValue } from 'react-native-reanimated';
32

43
import type {
54
ActiveItemValuesContextType,
65
Dimensions,
76
Vector
87
} from '../../types';
98
import { DragActivationState } from '../../types';
9+
import { useMutableValue } from '../../utils';
1010
import { createProvider } from '../utils';
1111

1212
type ActiveItemValuesProviderProps = {
@@ -17,19 +17,19 @@ const { ActiveItemValuesProvider, useActiveItemValuesContext } = createProvider(
1717
'ActiveItemValues'
1818
)<ActiveItemValuesProviderProps, ActiveItemValuesContextType>(() => {
1919
// POSITIONS
20-
const touchPosition = useSharedValue<null | Vector>(null);
21-
const activeItemPosition = useSharedValue<null | Vector>(null);
20+
const touchPosition = useMutableValue<null | Vector>(null);
21+
const activeItemPosition = useMutableValue<null | Vector>(null);
2222

2323
// DIMENSIONS
24-
const activeItemDimensions = useSharedValue<Dimensions | null>(null);
24+
const activeItemDimensions = useMutableValue<Dimensions | null>(null);
2525

2626
// DRAG STATE
27-
const activeItemKey = useSharedValue<null | string>(null);
28-
const prevActiveItemKey = useSharedValue<null | string>(null);
29-
const activationState = useSharedValue(DragActivationState.INACTIVE);
30-
const activeAnimationProgress = useSharedValue(0);
31-
const inactiveAnimationProgress = useSharedValue(0);
32-
const activeItemDropped = useSharedValue(true);
27+
const activeItemKey = useMutableValue<null | string>(null);
28+
const prevActiveItemKey = useMutableValue<null | string>(null);
29+
const activationState = useMutableValue(DragActivationState.INACTIVE);
30+
const activeAnimationProgress = useMutableValue(0);
31+
const inactiveAnimationProgress = useMutableValue(0);
32+
const activeItemDropped = useMutableValue(true);
3333

3434
return {
3535
value: {

0 commit comments

Comments
 (0)