Skip to content

Commit

Permalink
Replace setImmediate and requestAnimationFrame with `queueMicrota…
Browse files Browse the repository at this point in the history
…sk` (#2467)

## Description

- `setImmediate` is not available on web & when using
server-side-rendering
- `requestAnimationFrame` is not available when using
server-side-rendering

## Test plan

Test on Example and FabricExample apps.
  • Loading branch information
j-piasecki committed Apr 27, 2023
1 parent 651ee82 commit df98233
Show file tree
Hide file tree
Showing 4 changed files with 23 additions and 25 deletions.
20 changes: 11 additions & 9 deletions src/handlers/createHandler.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -174,16 +174,17 @@ export default function createHandler<
private handlerTag: number;
private config: Record<string, unknown>;
private propsRef: React.MutableRefObject<unknown>;
private isMountedRef: React.MutableRefObject<boolean | null>;
private viewNode: any;
private viewTag?: number;
private updateEnqueued: ReturnType<typeof setImmediate> | null = null;
private inspectorToggleListener?: EmitterSubscription;

constructor(props: T & InternalEventHandlers) {
super(props);
this.handlerTag = getNextHandlerTag();
this.config = {};
this.propsRef = React.createRef();
this.isMountedRef = React.createRef();
this.state = { allowTouches };
if (props.id) {
if (handlerIDToTag[props.id] !== undefined) {
Expand All @@ -195,6 +196,7 @@ export default function createHandler<

componentDidMount() {
const props: HandlerProps<U> = this.props;
this.isMountedRef.current = true;

if (DEV_ON_ANDROID) {
this.inspectorToggleListener = DeviceEventEmitter.addListener(
Expand All @@ -209,11 +211,10 @@ export default function createHandler<
// If there are unresolved refs (e.g. ".current" has not yet been set)
// passed as `simultaneousHandlers` or `waitFor`, we enqueue a call to
// _update method that will try to update native handler props using
// setImmediate. This makes it so update() function gets called after all
// queueMicrotask. This makes it so update() function gets called after all
// react components are mounted and we expect the missing ref object to
// be resolved by then.
this.updateEnqueued = setImmediate(() => {
this.updateEnqueued = null;
queueMicrotask(() => {
this.update(UNRESOLVED_REFS_RETRY_LIMIT);
});
}
Expand All @@ -239,11 +240,9 @@ export default function createHandler<

componentWillUnmount() {
this.inspectorToggleListener?.remove();
this.isMountedRef.current = false;
RNGestureHandlerModule.dropGestureHandler(this.handlerTag);
scheduleFlushOperations();
if (this.updateEnqueued) {
clearImmediate(this.updateEnqueued);
}
// We can't use this.props.id directly due to TS generic type narrowing bug, see https://github.com/microsoft/TypeScript/issues/13995 for more context
const handlerID: string | undefined = this.props.id;
if (handlerID) {
Expand Down Expand Up @@ -367,14 +366,17 @@ export default function createHandler<
};

private update(remainingTries: number) {
if (!this.isMountedRef.current) {
return;
}

const props: HandlerProps<U> = this.props;

// When ref is set via a function i.e. `ref={(r) => refObject.current = r}` instead of
// `ref={refObject}` it's possible that it won't be resolved in time. Seems like trying
// again is easy enough fix.
if (hasUnresolvedRefs(props) && remainingTries > 0) {
this.updateEnqueued = setImmediate(() => {
this.updateEnqueued = null;
queueMicrotask(() => {
this.update(remainingTries - 1);
});
} else {
Expand Down
11 changes: 5 additions & 6 deletions src/handlers/gestureHandlerCommon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,16 +191,15 @@ export function findNodeHandle(
return findNodeHandleRN(node);
}

let scheduledFlushOperationsId: ReturnType<
typeof requestAnimationFrame
> | null = null;
let flushOperationsScheduled = false;

export function scheduleFlushOperations() {
if (scheduledFlushOperationsId === null) {
scheduledFlushOperationsId = requestAnimationFrame(() => {
if (!flushOperationsScheduled) {
flushOperationsScheduled = true;
queueMicrotask(() => {
RNGestureHandlerModule.flushOperations();

scheduledFlushOperationsId = null;
flushOperationsScheduled = false;
});
}
}
15 changes: 6 additions & 9 deletions src/handlers/gestures/GestureDetector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,6 @@ export type GestureConfigReference = {
useReanimatedHook: boolean;
};

const scheduleUpdate =
Platform.OS === 'web' ? requestAnimationFrame : setImmediate;

function convertToHandlerTag(ref: GestureRef): number {
if (typeof ref === 'number') {
return ref;
Expand Down Expand Up @@ -153,9 +150,9 @@ function attachHandlers({
preparedGesture.firstExecution = false;
}

// use scheduleUpdate to extract handlerTags, because all refs should be initialized
// use queueMicrotask to extract handlerTags, because all refs should be initialized
// when it's ran
scheduleUpdate(() => {
queueMicrotask(() => {
if (!mountedRef.current) {
return;
}
Expand All @@ -173,9 +170,9 @@ function attachHandlers({
registerHandler(handler.handlerTag, handler, handler.config.testId);
}

// use scheduleUpdate to extract handlerTags, because all refs should be initialized
// use queueMicrotask to extract handlerTags, because all refs should be initialized
// when it's ran
scheduleUpdate(() => {
queueMicrotask(() => {
if (!mountedRef.current) {
return;
}
Expand Down Expand Up @@ -260,10 +257,10 @@ function updateHandlers(
}
}

// use scheduleUpdate to extract handlerTags, because when it's ran, all refs should be updated
// use queueMicrotask to extract handlerTags, because when it's ran, all refs should be updated
// and handlerTags in BaseGesture references should be updated in the loop above (we need to wait
// in case of external relations)
scheduleUpdate(() => {
queueMicrotask(() => {
if (!mountedRef.current) {
return;
}
Expand Down
2 changes: 1 addition & 1 deletion src/web_hammer/GestureHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -508,7 +508,7 @@ abstract class GestureHandler {
.filter((v) => v);

if (shouldUseTouchEvents !== this.shouldUseTouchEvents(props)) {
requestAnimationFrame(() => {
queueMicrotask(() => {
// if the undelying event API needs to be changed, we need to unmount and mount
// the hammer instance again.
this.destroy();
Expand Down

0 comments on commit df98233

Please sign in to comment.