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

Commit

Permalink
feat: add integration with redux devtools extension
Browse files Browse the repository at this point in the history
Currently supports:
- Tracking actions and navigation state
- Time travel for navigation state

It doesn't do anything in production
  • Loading branch information
satya164 committed Aug 17, 2019
1 parent b7735af commit ca985bb
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 4 deletions.
5 changes: 4 additions & 1 deletion packages/core/src/NavigationBuilderContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ const NavigationBuilderContext = React.createContext<{
addActionListener?: (listener: ChildActionListener) => void;
addFocusedListener?: (listener: FocusedNavigationListener) => void;
onRouteFocus?: (key: string) => void;
}>({});
trackAction: (key: string, action: NavigationAction) => void;
}>({
trackAction: () => undefined,
});

export default NavigationBuilderContext;
24 changes: 22 additions & 2 deletions packages/core/src/NavigationContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as BaseActions from './BaseActions';
import EnsureSingleNavigator from './EnsureSingleNavigator';
import NavigationBuilderContext from './NavigationBuilderContext';
import useFocusedListeners from './useFocusedListeners';
import useDevTools from './useDevTools';

import {
Route,
Expand Down Expand Up @@ -133,12 +134,25 @@ const Container = React.forwardRef(function NavigationContainer(
const transactionStateRef = React.useRef<State | null>(null);
const isTransactionActiveRef = React.useRef<boolean>(false);
const isFirstMountRef = React.useRef<boolean>(true);
const skipTrackingRef = React.useRef<boolean>(false);

const reset = React.useCallback((state: NavigationState) => {
skipTrackingRef.current = true;
setNavigationState(state);
}, []);

const { trackState, trackAction } = useDevTools({
name: '@navigation-ex',
reset,
state,
});

const builderContext = React.useMemo(
() => ({
addFocusedListener,
trackAction,
}),
[addFocusedListener]
[addFocusedListener, trackAction]
);

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

React.useEffect(() => {
if (skipTrackingRef.current) {
skipTrackingRef.current = false;
} else {
trackState(state);
}

navigationStateRef.current = state;
transactionStateRef.current = null;

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

isFirstMountRef.current = false;
}, [state, onStateChange]);
}, [state, onStateChange, trackState]);

return (
<NavigationBuilderContext.Provider value={builderContext}>
Expand Down
11 changes: 10 additions & 1 deletion packages/core/src/useDescriptors.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export default function useDescriptors<
emitter,
}: Options<ScreenOptions>) {
const [options, setOptions] = React.useState<{ [key: string]: object }>({});
const { trackAction } = React.useContext(NavigationBuilderContext);

const context = React.useMemo(
() => ({
Expand All @@ -69,8 +70,16 @@ export default function useDescriptors<
addActionListener,
addFocusedListener,
onRouteFocus,
trackAction,
}),
[navigation, onAction, addActionListener, addFocusedListener, onRouteFocus]
[
navigation,
onAction,
addActionListener,
addFocusedListener,
onRouteFocus,
trackAction,
]
);

const navigations = useNavigationCache<State, ScreenOptions>({
Expand Down
101 changes: 101 additions & 0 deletions packages/core/src/useDevTools.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import * as React from 'react';
import { NavigationState, NavigationAction, PartialState } from './types';

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

type Options = {
name: string;
reset: (state: NavigationState) => void;
state: State;
};

type DevTools = {
init(value: any): void;
send(action: any, value: any): void;
subscribe(
listener: (message: { type: string; [key: string]: any }) => void
): () => void;
};

declare global {
namespace NodeJS {
interface Global {
__REDUX_DEVTOOLS_EXTENSION__:
| {
connect(options: { name: string }): DevTools;
disconnect(): void;
}
| undefined;
}
}
}

export default function useDevTools({ name, reset, state }: Options) {
const devToolsRef = React.useRef<DevTools>();

if (
process.env.NODE_ENV !== 'production' &&
global.__REDUX_DEVTOOLS_EXTENSION__ &&
devToolsRef.current === undefined
) {
devToolsRef.current = global.__REDUX_DEVTOOLS_EXTENSION__.connect({ name });
}

const devTools = devToolsRef.current;
const lastStateRef = React.useRef<State>(state);
const actions = React.useRef<
Array<{ key: string; action: NavigationAction }>
>([]);

React.useEffect(() => {
devTools && devTools.init(lastStateRef.current);
}, [devTools]);

React.useEffect(
() =>
devTools &&
devTools.subscribe(message => {
if (message.type === 'DISPATCH' && message.state) {
reset(JSON.parse(message.state));
}
}),
[devTools, reset]
);

const trackState = React.useCallback(
(state: State) => {
if (!devTools) {
return;
}

while (actions.current.length > 1) {
devTools.send(actions.current.shift(), lastStateRef.current);
}

if (actions.current.length) {
devTools.send(actions.current.pop(), state);
} else if (lastStateRef.current !== state) {
devTools.send('@@UNKNOWN', state);
}

lastStateRef.current = state;
},
[devTools]
);

const trackAction = React.useCallback(
(key: string, action: NavigationAction) => {
if (!devTools) {
return;
}

actions.current.push({ key, action });
},
[devTools]
);

return {
trackAction,
trackState,
};
}
4 changes: 4 additions & 0 deletions packages/core/src/useOnAction.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export default function useOnAction({
onAction: onActionParent,
onRouteFocus: onRouteFocusParent,
addActionListener: addActionListenerParent,
trackAction,
} = React.useContext(NavigationBuilderContext);

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

if (result !== null) {
trackAction(state.key, action);

if (state !== result) {
setState(result);
}
Expand Down Expand Up @@ -99,6 +102,7 @@ export default function useOnAction({
getState,
router,
onActionParent,
trackAction,
onRouteFocusParent,
setState,
key,
Expand Down

0 comments on commit ca985bb

Please sign in to comment.