diff --git a/docs/docs/api/gestures/gesture.md b/docs/docs/api/gestures/gesture.md index a84a48003a..b8131ffc58 100644 --- a/docs/docs/api/gestures/gesture.md +++ b/docs/docs/api/gestures/gesture.md @@ -6,6 +6,20 @@ sidebar_label: Gesture `Gesture` is the object that allows you to create and compose gestures. +:::tip +Consider wrapping your gesture configurations with `useMemo`, as it will reduce the amount of work Gesture Handler has to do under the hood when updating gestures. For example: +```jsx +const gesture = useMemo( + () => + Gesture.Tap().onStart(() => { + console.log('Number of taps:', tapNumber + 1); + setTapNumber((value) => value + 1); + }), + [tapNumber, setTapNumber] +); +``` +::: + ### Gesture.Tap() Creates a new instance of [`TapGesture`](./tap-gesture.md) with its default config and no callbacks. diff --git a/src/handlers/gestures/GestureDetector.tsx b/src/handlers/gestures/GestureDetector.tsx index 8676173bdd..aaee89c389 100644 --- a/src/handlers/gestures/GestureDetector.tsx +++ b/src/handlers/gestures/GestureDetector.tsx @@ -268,11 +268,34 @@ function updateHandlers( } if (preparedGesture.animatedHandlers) { - preparedGesture.animatedHandlers.value = (preparedGesture.config + const previousHandlersValue = + preparedGesture.animatedHandlers.value ?? []; + const newHandlersValue = (preparedGesture.config .filter((g) => g.shouldUseReanimated) // ignore gestures that shouldn't run on UI .map((g) => g.handlers) as unknown) as HandlerCallbacks< Record >[]; + + // if amount of gesture configs changes, we need to update the callbacks in shared value + let shouldUpdateSharedValue = + previousHandlersValue.length !== newHandlersValue.length; + + if (!shouldUpdateSharedValue) { + // if the amount is the same, we need to check if any of the configs inside has changed + for (let i = 0; i < newHandlersValue.length; i++) { + if ( + // we can use the `gestureId` prop as it's unique for every config instance + newHandlersValue[i].gestureId !== previousHandlersValue[i].gestureId + ) { + shouldUpdateSharedValue = true; + break; + } + } + } + + if (shouldUpdateSharedValue) { + preparedGesture.animatedHandlers.value = newHandlersValue; + } } scheduleFlushOperations(); diff --git a/src/handlers/gestures/gesture.ts b/src/handlers/gestures/gesture.ts index 6f06c833cd..fcfcddc6ce 100644 --- a/src/handlers/gestures/gesture.ts +++ b/src/handlers/gestures/gesture.ts @@ -53,6 +53,7 @@ type TouchEventHandlerType = ( ) => void; export type HandlerCallbacks> = { + gestureId: number; handlerTag: number; onBegin?: (event: GestureStateChangeEvent) => void; onStart?: (event: GestureStateChangeEvent) => void; @@ -115,17 +116,32 @@ export abstract class Gesture { abstract prepare(): void; } +let nextGestureId = 0; export abstract class BaseGesture< EventPayloadT extends Record > extends Gesture { + private gestureId = -1; public handlerTag = -1; public handlerName = ''; public config: BaseGestureConfig = {}; public handlers: HandlerCallbacks = { + gestureId: -1, handlerTag: -1, isWorklet: [], }; + constructor() { + super(); + + // Used to check whether the gesture config has been updated when wrapping it + // with `useMemo`. Since every config will have a unique id, when the dependencies + // don't change, the config won't be recreated and the id will stay the same. + // If the id is different, it means that the config has changed and the gesture + // needs to be updated. + this.gestureId = nextGestureId++; + this.handlers.gestureId = this.gestureId; + } + private addDependency( key: 'simultaneousWith' | 'requireToFail', gesture: Exclude