1
1
import React , { PropsWithChildren , useCallback } from 'react' ;
2
- import { LayoutChangeEvent , StyleProp , ViewStyle } from 'react-native' ;
2
+ import { LayoutChangeEvent } from 'react-native' ;
3
3
import { Gesture , GestureDetector } from 'react-native-gesture-handler' ;
4
- import Animated , {
4
+ import {
5
5
runOnJS ,
6
6
useAnimatedReaction ,
7
7
useAnimatedStyle ,
8
8
useSharedValue ,
9
9
withSpring ,
10
10
withTiming
11
11
} from 'react-native-reanimated' ;
12
- import usePresenter , { ItemsOrder , animationConfig } from './usePresenter' ;
12
+ import _ from 'lodash' ;
13
+ import { useDidUpdate } from 'hooks' ;
14
+ import usePresenter , { animationConfig } from './usePresenter' ;
15
+ import { SortableItemProps } from './types' ;
13
16
import View from '../view' ;
14
17
15
- interface SortableItemProps extends ReturnType < typeof usePresenter > {
16
- index : number ;
17
- itemsOrder : Animated . SharedValue < ItemsOrder > ;
18
- onChange : ( ) => void ;
19
- style : StyleProp < ViewStyle > ;
20
- }
21
-
22
- function SortableItem ( props : PropsWithChildren < SortableItemProps > ) {
18
+ function SortableItem ( props : PropsWithChildren < SortableItemProps & ReturnType < typeof usePresenter > > ) {
23
19
const {
24
- index,
20
+ data,
21
+ id,
25
22
itemsOrder,
26
23
onChange,
27
24
style,
@@ -31,6 +28,7 @@ function SortableItem(props: PropsWithChildren<SortableItemProps>) {
31
28
getTranslationByOrderChange,
32
29
updateItemLayout
33
30
} = props ;
31
+ const initialIndex = useSharedValue ( _ . map ( data , 'id' ) . indexOf ( id ) ) ;
34
32
const translateX = useSharedValue ( 0 ) ;
35
33
const translateY = useSharedValue ( 0 ) ;
36
34
@@ -40,24 +38,51 @@ function SortableItem(props: PropsWithChildren<SortableItemProps>) {
40
38
const tempTranslateX = useSharedValue ( 0 ) ;
41
39
const tempTranslateY = useSharedValue ( 0 ) ;
42
40
41
+ const dataManuallyChanged = useSharedValue ( false ) ;
42
+
43
+ useDidUpdate ( ( ) => {
44
+ dataManuallyChanged . value = true ;
45
+ initialIndex . value = _ . map ( data , 'id' ) . indexOf ( id ) ;
46
+ } , [ data ] ) ;
47
+
48
+ useAnimatedReaction ( ( ) => itemsOrder . value ,
49
+ ( currItemsOrder , prevItemsOrder ) => {
50
+ // Note: Unfortunately itemsOrder sharedValue is being initialized on each render
51
+ // Therefore I added this extra check here that compares current and previous values
52
+ // See open issue: https://github.com/software-mansion/react-native-reanimated/issues/3224
53
+ if ( prevItemsOrder === null || currItemsOrder . join ( ',' ) === prevItemsOrder . join ( ',' ) ) {
54
+ return ;
55
+ } else {
56
+ const newOrder = getItemOrderById ( currItemsOrder , id ) ;
57
+ const prevOrder = getItemOrderById ( prevItemsOrder , id ) ;
58
+
59
+ /* In case the order of the item has returned back to its initial index we reset its position */
60
+ if ( newOrder === initialIndex . value ) {
61
+ /* Reset without an animation when the change is due to manual data change */
62
+ if ( dataManuallyChanged . value ) {
63
+ translateX . value = 0 ;
64
+ translateY . value = 0 ;
65
+ /* Reset with an animation when the change id due to user reordering */
66
+ } else {
67
+ translateX . value = withTiming ( 0 , animationConfig ) ;
68
+ translateY . value = withTiming ( 0 , animationConfig ) ;
69
+ }
70
+ dataManuallyChanged . value = false ;
71
+ /* Handle an order change, animate item to its new position */
72
+ } else if ( newOrder !== prevOrder ) {
73
+ const translation = getTranslationByOrderChange ( newOrder , prevOrder ) ;
74
+ translateX . value = withTiming ( translateX . value + translation . x , animationConfig ) ;
75
+ translateY . value = withTiming ( translateY . value + translation . y , animationConfig ) ;
76
+ }
77
+ }
78
+ } ) ;
79
+
43
80
const onLayout = useCallback ( ( event : LayoutChangeEvent ) => {
44
81
'worklet' ;
45
82
const { width, height} = event . nativeEvent . layout ;
46
- updateItemLayout ( index , { width, height} ) ;
83
+ updateItemLayout ( { width, height} ) ;
47
84
} , [ ] ) ;
48
85
49
- useAnimatedReaction ( ( ) => itemsOrder . value . indexOf ( index ) , // Note: It doesn't work with the getItemOrderById util
50
- ( newOrder , prevOrder ) => {
51
- if ( prevOrder !== null && newOrder !== prevOrder ) {
52
- const translation = getTranslationByOrderChange ( newOrder , prevOrder ) ;
53
- translateX . value = withTiming ( translateX . value + translation . x , animationConfig ) ;
54
- translateY . value = withTiming ( translateY . value + translation . y , animationConfig ) ;
55
- } else if ( newOrder === index ) {
56
- translateX . value = withTiming ( 0 , animationConfig ) ;
57
- translateY . value = withTiming ( 0 , animationConfig ) ;
58
- }
59
- } ) ;
60
-
61
86
const longPressGesture = Gesture . LongPress ( )
62
87
. onStart ( ( ) => {
63
88
isFloating . value = true ;
@@ -90,23 +115,23 @@ function SortableItem(props: PropsWithChildren<SortableItemProps>) {
90
115
translateY . value = tempTranslateY . value + event . translationY ;
91
116
92
117
// Swapping items
93
- const oldOrder = getItemOrderById ( itemsOrder . value , index ) ;
94
- const newOrder = getOrderByPosition ( translateX . value , translateY . value ) + index ;
118
+ const oldOrder = getItemOrderById ( itemsOrder . value , id ) ;
119
+ const newOrder = getOrderByPosition ( translateX . value , translateY . value ) + initialIndex . value ;
95
120
96
121
if ( oldOrder !== newOrder ) {
97
122
const itemIdToSwap = getIdByItemOrder ( itemsOrder . value , newOrder ) ;
98
123
99
124
if ( itemIdToSwap !== undefined ) {
100
125
const newItemsOrder = [ ...itemsOrder . value ] ;
101
- newItemsOrder [ newOrder ] = index ;
126
+ newItemsOrder [ newOrder ] = id ;
102
127
newItemsOrder [ oldOrder ] = itemIdToSwap ;
103
128
itemsOrder . value = newItemsOrder ;
104
129
}
105
130
}
106
131
} )
107
132
. onEnd ( ( ) => {
108
- const translation = getTranslationByOrderChange ( getItemOrderById ( itemsOrder . value , index ) ,
109
- getItemOrderById ( tempItemsOrder . value , index ) ) ;
133
+ const translation = getTranslationByOrderChange ( getItemOrderById ( itemsOrder . value , id ) ,
134
+ getItemOrderById ( tempItemsOrder . value , id ) ) ;
110
135
111
136
translateX . value = withTiming ( tempTranslateX . value + translation . x , animationConfig ) ;
112
137
translateY . value = withTiming ( tempTranslateY . value + translation . y , animationConfig ) ;
@@ -133,6 +158,7 @@ function SortableItem(props: PropsWithChildren<SortableItemProps>) {
133
158
transform : [ { translateX : translateX . value } , { translateY : translateY . value } , { scale} ]
134
159
} ;
135
160
} ) ;
161
+
136
162
return (
137
163
< View reanimated style = { [ style , animatedStyle ] } onLayout = { onLayout } >
138
164
{ /* @ts -expect-error related to children type issue that started on react 18 */ }
@@ -143,4 +169,4 @@ function SortableItem(props: PropsWithChildren<SortableItemProps>) {
143
169
) ;
144
170
}
145
171
146
- export default SortableItem ;
172
+ export default React . memo ( SortableItem ) ;
0 commit comments