Skip to content

Commit 95b044e

Browse files
authored
feat: add devtools package (react-navigation#8436)
The `devtools` package extracts the redux devtools extension integration to a separate package. In future we can add more tools such as flipper integration to this package. Usage: ```js import * as React from 'react'; import { NavigationContainer } from '@react-navigation/native'; import { useReduxDevToolsExtension } from '@react-navigation/devtools'; export default function App() { const navigationRef = React.useRef(); useReduxDevToolsExtension(navigationRef); return ( <NavigationContainer ref={navigationRef}>{/* ... */}</NavigationContainer> ); } ```
1 parent f51f9c8 commit 95b044e

17 files changed

+491
-173
lines changed

.eslintrc.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
"@react-navigation/drawer",
1212
"@react-navigation/bottom-tabs",
1313
"@react-navigation/material-top-tabs",
14-
"@react-navigation/material-bottom-tabs"
14+
"@react-navigation/material-bottom-tabs",
15+
"@react-navigation/devtools"
1516
]
1617
},
1718
"env": { "browser": true, "node": true }

README.md

+11-10
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,17 @@ If you are looking for version 4, the code can be found in the [4.x branch](http
1212

1313
## Package Versions
1414

15-
| Name | Latest Version |
16-
| ------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
17-
| [@react-navigation/core](/packages/core) | [![badge](https://img.shields.io/npm/v/@react-navigation/core.svg?style=flat-square)](https://www.npmjs.com/package/@react-navigation/core) |
18-
| [@react-navigation/native](/packages/native) | [![badge](https://img.shields.io/npm/v/@react-navigation/native.svg?style=flat-square)](https://www.npmjs.com/package/@react-navigation/native) |
19-
| [@react-navigation/routers](/packages/routers) | [![badge](https://img.shields.io/npm/v/@react-navigation/routers.svg?style=flat-square)](https://www.npmjs.com/package/@react-navigation/routers) |
20-
| [@react-navigation/stack](/packages/stack) | [![badge](https://img.shields.io/npm/v/@react-navigation/stack.svg?style=flat-square)](https://www.npmjs.com/package/@react-navigation/stack) |
21-
| [@react-navigation/drawer](/packages/drawer) | [![badge](https://img.shields.io/npm/v/@react-navigation/drawer.svg?style=flat-square)](https://www.npmjs.com/package/@react-navigation/drawer) |
22-
| [@react-navigation/material-top-tabs](/packages/material-top-tabs) | [![badge](https://img.shields.io/npm/v/@react-navigation/material-top-tabs.svg?style=flat-square)](https://www.npmjs.com/package/@react-navigation/material-top-tabs) |
23-
| [@react-navigation/material-bottom-tabs](/packages/material-bottom-tabs) | [![badge](https://img.shields.io/npm/v/@react-navigation/material-bottom-tabs.svg?style=flat-square)](https://www.npmjs.com/package/@react-navigation/material-bottom-tabs) |
24-
| [@react-navigation/bottom-tabs](/packages/bottom-tabs) | [![badge](https://img.shields.io/npm/v/@react-navigation/bottom-tabs.svg?style=flat-square)](https://www.npmjs.com/package/@react-navigation/bottom-tabs) |
15+
| Name | Latest Version |
16+
| ------------------------------------------------------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
17+
| [@react-navigation/core](/packages/core) | [![badge](https://img.shields.io/npm/v/@react-navigation/core.svg?style=flat-square)](https://www.npmjs.com/package/@react-navigation/core) |
18+
| [@react-navigation/native](/packages/native) | [![badge](https://img.shields.io/npm/v/@react-navigation/native.svg?style=flat-square)](https://www.npmjs.com/package/@react-navigation/native) |
19+
| [@react-navigation/routers](/packages/routers) | [![badge](https://img.shields.io/npm/v/@react-navigation/routers.svg?style=flat-square)](https://www.npmjs.com/package/@react-navigation/routers) |
20+
| [@react-navigation/stack](/packages/stack) | [![badge](https://img.shields.io/npm/v/@react-navigation/stack.svg?style=flat-square)](https://www.npmjs.com/package/@react-navigation/stack) |
21+
| [@react-navigation/drawer](/packages/drawer) | [![badge](https://img.shields.io/npm/v/@react-navigation/drawer.svg?style=flat-square)](https://www.npmjs.com/package/@react-navigation/drawer) |
22+
| [@react-navigation/material-top-tabs](/packages/material-top-tabs) | [![badge](https://img.shields.io/npm/v/@react-navigation/material-top-tabs.svg?style=flat-square)](https://www.npmjs.com/package/@react-navigation/material-top-tabs) |
23+
| [@react-navigation/material-bottom-tabs](/packages/material-bottom-tabs) | [![badge](https://img.shields.io/npm/v/@react-navigation/material-bottom-tabs.svg?style=flat-square)](https://www.npmjs.com/package/@react-navigation/material-bottom-tabs) |
24+
| [@react-navigation/bottom-tabs](/packages/bottom-tabs) | [![badge](https://img.shields.io/npm/v/@react-navigation/bottom-tabs.svg?style=flat-square)](https://www.npmjs.com/package/@react-navigation/bottom-tabs) |
25+
| [@react-navigation/devtools](/packages/devtools) | [![badge](https://img.shields.io/npm/v/@react-navigation/devtools.svg?style=flat-square)](https://www.npmjs.com/package/@react-navigation/devtools) |
2526

2627
## Contributing
2728

example/src/index.tsx

+7-3
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
DefaultTheme,
2828
DarkTheme,
2929
PathConfig,
30+
NavigationContainerRef,
3031
} from '@react-navigation/native';
3132
import {
3233
createDrawerNavigator,
@@ -37,6 +38,7 @@ import {
3738
StackNavigationProp,
3839
HeaderStyleInterpolators,
3940
} from '@react-navigation/stack';
41+
import { useReduxDevToolsExtension } from '@react-navigation/devtools';
4042

4143
import { restartApp } from './Restart';
4244
import AsyncStorage from './AsyncStorage';
@@ -60,9 +62,6 @@ YellowBox.ignoreWarnings(['Require cycle:', 'Warning: Async Storage']);
6062

6163
enableScreens();
6264

63-
// @ts-ignore
64-
global.REACT_NAVIGATION_REDUX_DEVTOOLS_EXTENSION_INTEGRATION_ENABLED = true;
65-
6665
type RootDrawerParamList = {
6766
Root: undefined;
6867
Another: undefined;
@@ -192,6 +191,10 @@ export default function App() {
192191
return () => Dimensions.removeEventListener('change', onDimensionsChange);
193192
}, []);
194193

194+
const navigationRef = React.useRef<NavigationContainerRef>(null);
195+
196+
useReduxDevToolsExtension(navigationRef);
197+
195198
if (!isReady) {
196199
return null;
197200
}
@@ -204,6 +207,7 @@ export default function App() {
204207
<StatusBar barStyle={theme.dark ? 'light-content' : 'dark-content'} />
205208
)}
206209
<NavigationContainer
210+
ref={navigationRef}
207211
initialState={initialState}
208212
onStateChange={(state) =>
209213
AsyncStorage.setItem(

packages/core/src/BaseNavigationContainer.tsx

+27-37
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import EnsureSingleNavigator from './EnsureSingleNavigator';
1111
import NavigationBuilderContext from './NavigationBuilderContext';
1212
import { ScheduleUpdateContext } from './useScheduleUpdate';
1313
import useFocusedListeners from './useFocusedListeners';
14-
import useDevTools from './useDevTools';
1514
import useStateGetters from './useStateGetters';
1615
import useOptionsGetters from './useOptionsGetters';
1716
import useEventEmitter from './useEventEmitter';
@@ -23,14 +22,26 @@ import NavigationStateContext from './NavigationStateContext';
2322

2423
type State = NavigationState | PartialState<NavigationState> | undefined;
2524

26-
const DEVTOOLS_CONFIG_KEY =
27-
'REACT_NAVIGATION_REDUX_DEVTOOLS_EXTENSION_INTEGRATION_ENABLED';
28-
2925
const NOT_INITIALIZED_ERROR =
3026
"The 'navigation' object hasn't been initialized yet. This might happen if you don't have a navigator mounted, or if the navigator hasn't finished mounting. See https://reactnavigation.org/docs/navigating-without-navigation-prop#handling-initialization for more details.";
3127

3228
let hasWarnedForSerialization = false;
3329

30+
/**
31+
* Migration instructions for removal of devtools from core
32+
*/
33+
Object.defineProperty(
34+
global,
35+
'REACT_NAVIGATION_REDUX_DEVTOOLS_EXTENSION_INTEGRATION_ENABLED',
36+
{
37+
set(_) {
38+
console.warn(
39+
"Redux devtools extension integration can be enabled with the '@react-navigation/devtools' package. For more details, see https://reactnavigation.org/docs/devtools"
40+
);
41+
},
42+
}
43+
);
44+
3445
/**
3546
* Remove `key` and `routeNames` from the state objects recursively to get partial state.
3647
*
@@ -100,7 +111,6 @@ const BaseNavigationContainer = React.forwardRef(
100111
);
101112

102113
const isFirstMountRef = React.useRef<boolean>(true);
103-
const skipTrackingRef = React.useRef<boolean>(false);
104114

105115
const navigatorKeyRef = React.useRef<string | undefined>();
106116

@@ -110,23 +120,6 @@ const BaseNavigationContainer = React.forwardRef(
110120
navigatorKeyRef.current = key;
111121
}, []);
112122

113-
const reset = React.useCallback(
114-
(state: NavigationState) => {
115-
skipTrackingRef.current = true;
116-
setState(state);
117-
},
118-
[setState]
119-
);
120-
121-
const { trackState, trackAction } = useDevTools({
122-
enabled:
123-
// @ts-ignore
124-
DEVTOOLS_CONFIG_KEY in global ? global[DEVTOOLS_CONFIG_KEY] : false,
125-
name: '@react-navigation',
126-
reset,
127-
state,
128-
});
129-
130123
const {
131124
listeners,
132125
addListener: addFocusedListener,
@@ -162,10 +155,9 @@ const BaseNavigationContainer = React.forwardRef(
162155

163156
const resetRoot = React.useCallback(
164157
(state?: PartialState<NavigationState> | NavigationState) => {
165-
trackAction('@@RESET_ROOT');
166158
setState(state);
167159
},
168-
[setState, trackAction]
160+
[setState]
169161
);
170162

171163
const getRootState = React.useCallback(() => {
@@ -211,13 +203,20 @@ const BaseNavigationContainer = React.forwardRef(
211203
getCurrentOptions,
212204
}));
213205

206+
const onDispatchAction = React.useCallback(
207+
(action: NavigationAction, noop: boolean) => {
208+
emitter.emit({ type: '__unsafe_action__', data: { action, noop } });
209+
},
210+
[emitter]
211+
);
212+
214213
const builderContext = React.useMemo(
215214
() => ({
216215
addFocusedListener,
217216
addStateGetter,
218-
trackAction,
217+
onDispatchAction,
219218
}),
220-
[addFocusedListener, trackAction, addStateGetter]
219+
[addFocusedListener, addStateGetter, onDispatchAction]
221220
);
222221

223222
const scheduleContext = React.useMemo(
@@ -258,23 +257,14 @@ const BaseNavigationContainer = React.forwardRef(
258257
}
259258
}
260259

261-
emitter.emit({
262-
type: 'state',
263-
data: { state },
264-
});
265-
266-
if (skipTrackingRef.current) {
267-
skipTrackingRef.current = false;
268-
} else {
269-
trackState(getRootState);
270-
}
260+
emitter.emit({ type: 'state', data: { state } });
271261

272262
if (!isFirstMountRef.current && onStateChangeRef.current) {
273263
onStateChangeRef.current(getRootState());
274264
}
275265

276266
isFirstMountRef.current = false;
277-
}, [trackState, getRootState, emitter, state]);
267+
}, [getRootState, emitter, state]);
278268

279269
return (
280270
<ScheduleUpdateContext.Provider value={scheduleContext}>

packages/core/src/NavigationBuilderContext.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,10 @@ const NavigationBuilderContext = React.createContext<{
3232
addActionListener?: (listener: ChildActionListener) => void;
3333
addFocusedListener?: (listener: FocusedNavigationListener) => void;
3434
onRouteFocus?: (key: string) => void;
35+
onDispatchAction: (action: NavigationAction, noop: boolean) => void;
3536
addStateGetter?: (key: string, getter: NavigatorStateGetter) => void;
36-
trackAction: (action: NavigationAction) => void;
3737
}>({
38-
trackAction: () => undefined,
38+
onDispatchAction: () => undefined,
3939
});
4040

4141
export default NavigationBuilderContext;

packages/core/src/types.tsx

+32-1
Original file line numberDiff line numberDiff line change
@@ -410,8 +410,39 @@ export type RouteConfig<
410410
}
411411
);
412412

413+
export type NavigationContainerEventMap = {
414+
/**
415+
* Event which fires when the navigation state changes.
416+
*/
417+
state: {
418+
data: {
419+
/**
420+
* The updated state object after the state change.
421+
*/
422+
state: NavigationState;
423+
};
424+
};
425+
/**
426+
* Event which fires when an action is dispatched.
427+
* Only intended for debugging purposes, don't use it for app logic.
428+
* This event will be emitted before state changes have been applied.
429+
*/
430+
__unsafe_action__: {
431+
data: {
432+
/**
433+
* The action object which was dispatched.
434+
*/
435+
action: NavigationAction;
436+
/**
437+
* Whether the action was a no-op, i.e. resulted any state changes.
438+
*/
439+
noop: boolean;
440+
};
441+
};
442+
};
443+
413444
export type NavigationContainerRef = NavigationHelpers<ParamListBase> &
414-
EventConsumer<{ state: { data: { state: NavigationState } } }> & {
445+
EventConsumer<NavigationContainerEventMap> & {
415446
/**
416447
* Reset the navigation state of the root navigator to the provided state.
417448
*

packages/core/src/useDescriptors.tsx

+6-6
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ export default function useDescriptors<
8080
emitter,
8181
}: Options<State, ScreenOptions, EventMap>) {
8282
const [options, setOptions] = React.useState<Record<string, object>>({});
83-
const { trackAction } = React.useContext(NavigationBuilderContext);
83+
const { onDispatchAction } = React.useContext(NavigationBuilderContext);
8484

8585
const context = React.useMemo(
8686
() => ({
@@ -90,16 +90,16 @@ export default function useDescriptors<
9090
addFocusedListener,
9191
addStateGetter,
9292
onRouteFocus,
93-
trackAction,
93+
onDispatchAction,
9494
}),
9595
[
96-
navigation,
97-
onAction,
9896
addActionListener,
9997
addFocusedListener,
100-
onRouteFocus,
10198
addStateGetter,
102-
trackAction,
99+
navigation,
100+
onAction,
101+
onDispatchAction,
102+
onRouteFocus,
103103
]
104104
);
105105

0 commit comments

Comments
 (0)