-
-
Notifications
You must be signed in to change notification settings - Fork 5k
/
useOnPreventRemove.tsx
98 lines (82 loc) · 2.73 KB
/
useOnPreventRemove.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
import * as React from 'react';
import type {
NavigationState,
NavigationAction,
} from '@react-navigation/routers';
import NavigationBuilderContext, {
ChildBeforeRemoveListener,
} from './NavigationBuilderContext';
import NavigationRouteContext from './NavigationRouteContext';
import type { NavigationEventEmitter } from './useEventEmitter';
import type { EventMapCore } from './types';
type Options = {
getState: () => NavigationState;
emitter: NavigationEventEmitter<EventMapCore<any>>;
beforeRemoveListeners: Record<string, ChildBeforeRemoveListener | undefined>;
};
const VISITED_ROUTE_KEYS = Symbol('VISITED_ROUTE_KEYS');
export const shouldPreventRemove = (
emitter: NavigationEventEmitter<EventMapCore<any>>,
beforeRemoveListeners: Record<string, ChildBeforeRemoveListener | undefined>,
currentRoutes: { key: string }[],
nextRoutes: { key?: string | undefined }[],
action: NavigationAction
) => {
const nextRouteKeys = nextRoutes.map((route) => route.key);
// Call these in reverse order so last screens handle the event first
const removedRoutes = currentRoutes
.filter((route) => !nextRouteKeys.includes(route.key))
.reverse();
const visitedRouteKeys: Set<string> =
// @ts-expect-error: add this property to mark that we've already emitted this action
action[VISITED_ROUTE_KEYS] ?? new Set<string>();
const beforeRemoveAction = {
...action,
[VISITED_ROUTE_KEYS]: visitedRouteKeys,
};
for (const route of removedRoutes) {
if (visitedRouteKeys.has(route.key)) {
// Skip if we've already emitted this action for this screen
continue;
}
// First, we need to check if any child screens want to prevent it
const isPrevented = beforeRemoveListeners[route.key]?.(beforeRemoveAction);
if (isPrevented) {
return true;
}
visitedRouteKeys.add(route.key);
const event = emitter.emit({
type: 'beforeRemove',
target: route.key,
data: { action: beforeRemoveAction },
canPreventDefault: true,
});
if (event.defaultPrevented) {
return true;
}
}
return false;
};
export default function useOnPreventRemove({
getState,
emitter,
beforeRemoveListeners,
}: Options) {
const { addKeyedListener } = React.useContext(NavigationBuilderContext);
const route = React.useContext(NavigationRouteContext);
const routeKey = route?.key;
React.useEffect(() => {
if (routeKey) {
return addKeyedListener?.('beforeRemove', routeKey, (action) => {
const state = getState();
return shouldPreventRemove(
emitter,
beforeRemoveListeners,
state.routes,
[],
action
);
});
}
}, [addKeyedListener, beforeRemoveListeners, emitter, getState, routeKey]);
}