Skip to content
This repository was archived by the owner on Feb 8, 2020. It is now read-only.

Commit ca985bb

Browse files
committed
feat: add integration with redux devtools extension
Currently supports: - Tracking actions and navigation state - Time travel for navigation state It doesn't do anything in production
1 parent b7735af commit ca985bb

File tree

5 files changed

+141
-4
lines changed

5 files changed

+141
-4
lines changed

packages/core/src/NavigationBuilderContext.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ const NavigationBuilderContext = React.createContext<{
2525
addActionListener?: (listener: ChildActionListener) => void;
2626
addFocusedListener?: (listener: FocusedNavigationListener) => void;
2727
onRouteFocus?: (key: string) => void;
28-
}>({});
28+
trackAction: (key: string, action: NavigationAction) => void;
29+
}>({
30+
trackAction: () => undefined,
31+
});
2932

3033
export default NavigationBuilderContext;

packages/core/src/NavigationContainer.tsx

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as BaseActions from './BaseActions';
33
import EnsureSingleNavigator from './EnsureSingleNavigator';
44
import NavigationBuilderContext from './NavigationBuilderContext';
55
import useFocusedListeners from './useFocusedListeners';
6+
import useDevTools from './useDevTools';
67

78
import {
89
Route,
@@ -133,12 +134,25 @@ const Container = React.forwardRef(function NavigationContainer(
133134
const transactionStateRef = React.useRef<State | null>(null);
134135
const isTransactionActiveRef = React.useRef<boolean>(false);
135136
const isFirstMountRef = React.useRef<boolean>(true);
137+
const skipTrackingRef = React.useRef<boolean>(false);
138+
139+
const reset = React.useCallback((state: NavigationState) => {
140+
skipTrackingRef.current = true;
141+
setNavigationState(state);
142+
}, []);
143+
144+
const { trackState, trackAction } = useDevTools({
145+
name: '@navigation-ex',
146+
reset,
147+
state,
148+
});
136149

137150
const builderContext = React.useMemo(
138151
() => ({
139152
addFocusedListener,
153+
trackAction,
140154
}),
141-
[addFocusedListener]
155+
[addFocusedListener, trackAction]
142156
);
143157

144158
const performTransaction = React.useCallback((callback: () => void) => {
@@ -189,6 +203,12 @@ const Container = React.forwardRef(function NavigationContainer(
189203
);
190204

191205
React.useEffect(() => {
206+
if (skipTrackingRef.current) {
207+
skipTrackingRef.current = false;
208+
} else {
209+
trackState(state);
210+
}
211+
192212
navigationStateRef.current = state;
193213
transactionStateRef.current = null;
194214

@@ -197,7 +217,7 @@ const Container = React.forwardRef(function NavigationContainer(
197217
}
198218

199219
isFirstMountRef.current = false;
200-
}, [state, onStateChange]);
220+
}, [state, onStateChange, trackState]);
201221

202222
return (
203223
<NavigationBuilderContext.Provider value={builderContext}>

packages/core/src/useDescriptors.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ export default function useDescriptors<
6161
emitter,
6262
}: Options<ScreenOptions>) {
6363
const [options, setOptions] = React.useState<{ [key: string]: object }>({});
64+
const { trackAction } = React.useContext(NavigationBuilderContext);
6465

6566
const context = React.useMemo(
6667
() => ({
@@ -69,8 +70,16 @@ export default function useDescriptors<
6970
addActionListener,
7071
addFocusedListener,
7172
onRouteFocus,
73+
trackAction,
7274
}),
73-
[navigation, onAction, addActionListener, addFocusedListener, onRouteFocus]
75+
[
76+
navigation,
77+
onAction,
78+
addActionListener,
79+
addFocusedListener,
80+
onRouteFocus,
81+
trackAction,
82+
]
7483
);
7584

7685
const navigations = useNavigationCache<State, ScreenOptions>({

packages/core/src/useDevTools.tsx

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import * as React from 'react';
2+
import { NavigationState, NavigationAction, PartialState } from './types';
3+
4+
type State = NavigationState | PartialState<NavigationState> | undefined;
5+
6+
type Options = {
7+
name: string;
8+
reset: (state: NavigationState) => void;
9+
state: State;
10+
};
11+
12+
type DevTools = {
13+
init(value: any): void;
14+
send(action: any, value: any): void;
15+
subscribe(
16+
listener: (message: { type: string; [key: string]: any }) => void
17+
): () => void;
18+
};
19+
20+
declare global {
21+
namespace NodeJS {
22+
interface Global {
23+
__REDUX_DEVTOOLS_EXTENSION__:
24+
| {
25+
connect(options: { name: string }): DevTools;
26+
disconnect(): void;
27+
}
28+
| undefined;
29+
}
30+
}
31+
}
32+
33+
export default function useDevTools({ name, reset, state }: Options) {
34+
const devToolsRef = React.useRef<DevTools>();
35+
36+
if (
37+
process.env.NODE_ENV !== 'production' &&
38+
global.__REDUX_DEVTOOLS_EXTENSION__ &&
39+
devToolsRef.current === undefined
40+
) {
41+
devToolsRef.current = global.__REDUX_DEVTOOLS_EXTENSION__.connect({ name });
42+
}
43+
44+
const devTools = devToolsRef.current;
45+
const lastStateRef = React.useRef<State>(state);
46+
const actions = React.useRef<
47+
Array<{ key: string; action: NavigationAction }>
48+
>([]);
49+
50+
React.useEffect(() => {
51+
devTools && devTools.init(lastStateRef.current);
52+
}, [devTools]);
53+
54+
React.useEffect(
55+
() =>
56+
devTools &&
57+
devTools.subscribe(message => {
58+
if (message.type === 'DISPATCH' && message.state) {
59+
reset(JSON.parse(message.state));
60+
}
61+
}),
62+
[devTools, reset]
63+
);
64+
65+
const trackState = React.useCallback(
66+
(state: State) => {
67+
if (!devTools) {
68+
return;
69+
}
70+
71+
while (actions.current.length > 1) {
72+
devTools.send(actions.current.shift(), lastStateRef.current);
73+
}
74+
75+
if (actions.current.length) {
76+
devTools.send(actions.current.pop(), state);
77+
} else if (lastStateRef.current !== state) {
78+
devTools.send('@@UNKNOWN', state);
79+
}
80+
81+
lastStateRef.current = state;
82+
},
83+
[devTools]
84+
);
85+
86+
const trackAction = React.useCallback(
87+
(key: string, action: NavigationAction) => {
88+
if (!devTools) {
89+
return;
90+
}
91+
92+
actions.current.push({ key, action });
93+
},
94+
[devTools]
95+
);
96+
97+
return {
98+
trackAction,
99+
trackState,
100+
};
101+
}

packages/core/src/useOnAction.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export default function useOnAction({
3232
onAction: onActionParent,
3333
onRouteFocus: onRouteFocusParent,
3434
addActionListener: addActionListenerParent,
35+
trackAction,
3536
} = React.useContext(NavigationBuilderContext);
3637

3738
const onAction = React.useCallback(
@@ -60,6 +61,8 @@ export default function useOnAction({
6061
result = result === null && action.target === state.key ? state : result;
6162

6263
if (result !== null) {
64+
trackAction(state.key, action);
65+
6366
if (state !== result) {
6467
setState(result);
6568
}
@@ -99,6 +102,7 @@ export default function useOnAction({
99102
getState,
100103
router,
101104
onActionParent,
105+
trackAction,
102106
onRouteFocusParent,
103107
setState,
104108
key,

0 commit comments

Comments
 (0)