mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-01-12 22:51:18 +08:00
fix: automatically queue listeners when container isn't ready
This commit is contained in:
@@ -192,12 +192,13 @@ const BaseNavigationContainer = React.forwardRef(
|
||||
return acc;
|
||||
}, {}),
|
||||
...emitter.create('root'),
|
||||
resetRoot,
|
||||
dispatch,
|
||||
resetRoot,
|
||||
isFocused: () => true,
|
||||
canGoBack,
|
||||
getRootState,
|
||||
getState: () => stateRef.current,
|
||||
getParent: () => undefined,
|
||||
getState: () => stateRef.current,
|
||||
getRootState,
|
||||
getCurrentRoute,
|
||||
getCurrentOptions,
|
||||
isReady: () => listeners.focus[0] != null,
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import type { NavigationState, ParamListBase } from '@react-navigation/routers';
|
||||
import { render } from '@testing-library/react-native';
|
||||
import * as React from 'react';
|
||||
|
||||
import BaseNavigationContainer from '../BaseNavigationContainer';
|
||||
import createNavigationContainerRef from '../createNavigationContainerRef';
|
||||
import Screen from '../Screen';
|
||||
import useNavigationBuilder from '../useNavigationBuilder';
|
||||
import MockRouter from './__fixtures__/MockRouter';
|
||||
|
||||
it('adds the listener even if container is mounted later', () => {
|
||||
const ref = createNavigationContainerRef<ParamListBase>();
|
||||
const listener = jest.fn();
|
||||
|
||||
ref.addListener('state', listener);
|
||||
|
||||
const TestNavigator = (props: any) => {
|
||||
const { state, descriptors } = useNavigationBuilder<
|
||||
NavigationState,
|
||||
any,
|
||||
{},
|
||||
{ title?: string },
|
||||
any
|
||||
>(MockRouter, props);
|
||||
const { render, options } = descriptors[state.routes[state.index].key];
|
||||
|
||||
return (
|
||||
<main>
|
||||
<h1>{options.title}</h1>
|
||||
<div>{render()}</div>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
const element = (
|
||||
<BaseNavigationContainer ref={ref}>
|
||||
<TestNavigator>
|
||||
<Screen name="foo">{() => null}</Screen>
|
||||
<Screen name="bar">{() => null}</Screen>
|
||||
</TestNavigator>
|
||||
</BaseNavigationContainer>
|
||||
);
|
||||
|
||||
render(element).update(element);
|
||||
|
||||
expect(listener).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
@@ -1,6 +1,10 @@
|
||||
import { CommonActions } from '@react-navigation/routers';
|
||||
|
||||
import type { NavigationContainerRefWithCurrent } from './types';
|
||||
import type {
|
||||
NavigationContainerEventMap,
|
||||
NavigationContainerRef,
|
||||
NavigationContainerRefWithCurrent,
|
||||
} from './types';
|
||||
|
||||
export const NOT_INITIALIZED_ERROR =
|
||||
"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.";
|
||||
@@ -14,6 +18,7 @@ export default function createNavigationContainerRef<
|
||||
'removeListener',
|
||||
'resetRoot',
|
||||
'dispatch',
|
||||
'isFocused',
|
||||
'canGoBack',
|
||||
'getRootState',
|
||||
'getState',
|
||||
@@ -22,26 +27,70 @@ export default function createNavigationContainerRef<
|
||||
'getCurrentOptions',
|
||||
] as const;
|
||||
|
||||
const listeners: Record<string, ((...args: any[]) => void)[]> = {};
|
||||
|
||||
const removeListener = (
|
||||
event: string,
|
||||
callback: (...args: any[]) => void
|
||||
) => {
|
||||
listeners[event] = listeners[event]?.filter((cb) => cb !== callback);
|
||||
};
|
||||
|
||||
let current: NavigationContainerRef<ParamList> | null = null;
|
||||
|
||||
const ref: NavigationContainerRefWithCurrent<ParamList> = {
|
||||
get current() {
|
||||
return current;
|
||||
},
|
||||
set current(value: NavigationContainerRef<ParamList> | null) {
|
||||
current = value;
|
||||
|
||||
if (value != null) {
|
||||
Object.entries(listeners).forEach(([event, callbacks]) => {
|
||||
callbacks.forEach((callback) => {
|
||||
value.addListener(
|
||||
event as keyof NavigationContainerEventMap,
|
||||
callback
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
isReady: () => {
|
||||
if (current == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return current.isReady();
|
||||
},
|
||||
...methods.reduce<any>((acc, name) => {
|
||||
acc[name] = (...args: any[]) => {
|
||||
if (ref.current == null) {
|
||||
console.error(NOT_INITIALIZED_ERROR);
|
||||
if (current == null) {
|
||||
switch (name) {
|
||||
case 'addListener': {
|
||||
const [event, callback] = args;
|
||||
|
||||
listeners[event] = listeners[event] || [];
|
||||
listeners[event].push(callback);
|
||||
|
||||
return () => removeListener(event, callback);
|
||||
}
|
||||
case 'removeListener': {
|
||||
const [event, callback] = args;
|
||||
|
||||
removeListener(event, callback);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
console.error(NOT_INITIALIZED_ERROR);
|
||||
}
|
||||
} else {
|
||||
// @ts-expect-error: this is ok
|
||||
return ref.current[name](...args);
|
||||
return current[name](...args);
|
||||
}
|
||||
};
|
||||
return acc;
|
||||
}, {}),
|
||||
isReady: () => {
|
||||
if (ref.current == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ref.current.isReady();
|
||||
},
|
||||
current: null,
|
||||
};
|
||||
|
||||
return ref;
|
||||
|
||||
Reference in New Issue
Block a user