diff --git a/example/src/Screens/AuthFlow.tsx b/example/src/Screens/AuthFlow.tsx
index 2117a27d..7068d88e 100644
--- a/example/src/Screens/AuthFlow.tsx
+++ b/example/src/Screens/AuthFlow.tsx
@@ -147,6 +147,10 @@ export default function SimpleStackScreen({
[]
);
+ if (state.isLoading) {
+ return ;
+ }
+
return (
- {state.isLoading ? (
-
- ) : state.userToken === undefined ? (
+ {state.userToken === undefined ? (
isInitialRef.current, []);
+
const context = React.useMemo(
() => ({
state,
@@ -254,14 +258,24 @@ const BaseNavigationContainer = React.forwardRef(
setState,
getKey,
setKey,
+ getIsInitial,
addOptionsGetter,
}),
- [getKey, getState, setKey, setState, state, addOptionsGetter]
+ [
+ state,
+ getState,
+ setState,
+ getKey,
+ setKey,
+ getIsInitial,
+ addOptionsGetter,
+ ]
);
const onStateChangeRef = React.useRef(onStateChange);
React.useEffect(() => {
+ isInitialRef.current = false;
onStateChangeRef.current = onStateChange;
});
diff --git a/packages/core/src/NavigationStateContext.tsx b/packages/core/src/NavigationStateContext.tsx
index d0efbc48..d4191f45 100644
--- a/packages/core/src/NavigationStateContext.tsx
+++ b/packages/core/src/NavigationStateContext.tsx
@@ -13,6 +13,7 @@ export default React.createContext<{
setState: (
state: NavigationState | PartialState | undefined
) => void;
+ getIsInitial: () => boolean;
addOptionsGetter?: (
key: string,
getter: () => object | undefined | null
@@ -32,4 +33,7 @@ export default React.createContext<{
get setState(): any {
throw new Error(MISSING_CONTEXT_ERROR);
},
+ get getIsInitial(): any {
+ throw new Error(MISSING_CONTEXT_ERROR);
+ },
});
diff --git a/packages/core/src/SceneView.tsx b/packages/core/src/SceneView.tsx
index 87e1c53f..df8cabdf 100644
--- a/packages/core/src/SceneView.tsx
+++ b/packages/core/src/SceneView.tsx
@@ -76,6 +76,14 @@ export default function SceneView<
[getState, route.key, setState]
);
+ const isInitialRef = React.useRef(true);
+
+ React.useEffect(() => {
+ isInitialRef.current = false;
+ });
+
+ const getIsInitial = React.useCallback(() => isInitialRef.current, []);
+
const context = React.useMemo(
() => ({
state: route.state,
@@ -83,14 +91,16 @@ export default function SceneView<
setState: setCurrentState,
getKey,
setKey,
+ getIsInitial,
addOptionsGetter,
}),
[
- getCurrentState,
- getKey,
route.state,
+ getCurrentState,
setCurrentState,
+ getKey,
setKey,
+ getIsInitial,
addOptionsGetter,
]
);
diff --git a/packages/core/src/__tests__/BaseNavigationContainer.test.tsx b/packages/core/src/__tests__/BaseNavigationContainer.test.tsx
index 5df2ff20..8f5a108b 100644
--- a/packages/core/src/__tests__/BaseNavigationContainer.test.tsx
+++ b/packages/core/src/__tests__/BaseNavigationContainer.test.tsx
@@ -366,6 +366,7 @@ it('handles getRootState', () => {
type: 'test',
});
});
+
it('emits state events when the state changes', () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
@@ -401,6 +402,7 @@ it('emits state events when the state changes', () => {
ref.current?.navigate('bar');
});
+ expect(listener).toBeCalledTimes(1);
expect(listener.mock.calls[0][0].data.state).toEqual({
type: 'test',
stale: false,
@@ -418,6 +420,7 @@ it('emits state events when the state changes', () => {
ref.current?.navigate('baz', { answer: 42 });
});
+ expect(listener).toBeCalledTimes(2);
expect(listener.mock.calls[1][0].data.state).toEqual({
type: 'test',
stale: false,
@@ -432,6 +435,97 @@ it('emits state events when the state changes', () => {
});
});
+it('emits state events when new navigator mounts', () => {
+ jest.useFakeTimers();
+
+ const TestNavigator = (props: any) => {
+ const { state, descriptors } = useNavigationBuilder(MockRouter, props);
+
+ return (
+
+ {state.routes.map((route) => descriptors[route.key].render())}
+
+ );
+ };
+
+ const ref = React.createRef();
+
+ const NestedNavigator = () => {
+ const [isRendered, setIsRendered] = React.useState(false);
+
+ React.useEffect(() => {
+ setTimeout(() => setIsRendered(true), 100);
+ }, []);
+
+ if (!isRendered) {
+ return null;
+ }
+
+ return (
+
+ {() => null}
+ {() => null}
+
+ );
+ };
+
+ const onStateChange = jest.fn();
+
+ const element = (
+
+
+ {() => null}
+
+
+
+ );
+
+ render(element).update(element);
+
+ const listener = jest.fn();
+
+ ref.current?.addListener('state', listener);
+
+ expect(listener).not.toHaveBeenCalled();
+ expect(onStateChange).not.toHaveBeenCalled();
+
+ act(() => {
+ jest.runAllTimers();
+ });
+
+ const resultState = {
+ stale: false,
+ type: 'test',
+ index: 0,
+ key: '10',
+ routeNames: ['foo', 'bar'],
+ routes: [
+ { key: 'foo', name: 'foo' },
+ {
+ key: 'bar',
+ name: 'bar',
+ state: {
+ stale: false,
+ type: 'test',
+ index: 0,
+ key: '11',
+ routeNames: ['baz', 'bax'],
+ routes: [
+ { key: 'baz', name: 'baz' },
+ { key: 'bax', name: 'bax' },
+ ],
+ },
+ },
+ ],
+ };
+
+ expect(listener).toBeCalledTimes(1);
+ expect(listener.mock.calls[0][0].data.state).toEqual(resultState);
+
+ expect(onStateChange).toBeCalledTimes(1);
+ expect(onStateChange).lastCalledWith(resultState);
+});
+
it('emits option events when options change with tab router', () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder(TabRouter, props);
diff --git a/packages/core/src/useNavigationBuilder.tsx b/packages/core/src/useNavigationBuilder.tsx
index 37d774a7..56520f4b 100644
--- a/packages/core/src/useNavigationBuilder.tsx
+++ b/packages/core/src/useNavigationBuilder.tsx
@@ -279,6 +279,7 @@ export default function useNavigationBuilder<
setState,
setKey,
getKey,
+ getIsInitial,
} = React.useContext(NavigationStateContext);
const [initializedState, isFirstStateInitialization] = React.useMemo(() => {
@@ -372,6 +373,13 @@ export default function useNavigationBuilder<
React.useEffect(() => {
setKey(navigatorKey);
+ if (!getIsInitial()) {
+ // If it's not initial render, we need to update the state
+ // This will make sure that our container gets notifier of state changes due to new mounts
+ // This is necessary for proper screen tracking, URL updates etc.
+ setState(nextState);
+ }
+
return () => {
// We need to clean up state for this navigator on unmount
// We do it in a timeout because we need to detect if another navigator mounted in the meantime