mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-01-12 22:51:18 +08:00
feat: add helper and hook for container ref
This commit is contained in:
@@ -28,7 +28,7 @@ import {
|
||||
DefaultTheme,
|
||||
DarkTheme,
|
||||
PathConfigMap,
|
||||
NavigationContainerRef,
|
||||
useNavigationContainerRef,
|
||||
} from '@react-navigation/native';
|
||||
import { createDrawerNavigator } from '@react-navigation/drawer';
|
||||
import {
|
||||
@@ -193,7 +193,7 @@ export default function App() {
|
||||
return () => Dimensions.removeEventListener('change', onDimensionsChange);
|
||||
}, []);
|
||||
|
||||
const navigationRef = React.useRef<NavigationContainerRef>(null);
|
||||
const navigationRef = useNavigationContainerRef<RootStackParamList>();
|
||||
|
||||
useReduxDevToolsExtension(navigationRef);
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
InitialState,
|
||||
PartialState,
|
||||
NavigationAction,
|
||||
ParamListBase,
|
||||
} from '@react-navigation/routers';
|
||||
import EnsureSingleNavigator from './EnsureSingleNavigator';
|
||||
import UnhandledActionContext from './UnhandledActionContext';
|
||||
@@ -22,6 +23,7 @@ import useSyncState from './useSyncState';
|
||||
import checkSerializable from './checkSerializable';
|
||||
import checkDuplicateRouteNames from './checkDuplicateRouteNames';
|
||||
import findFocusedRoute from './findFocusedRoute';
|
||||
import { NOT_INITIALIZED_ERROR } from './createNavigationContainerRef';
|
||||
import type {
|
||||
NavigationContainerEventMap,
|
||||
NavigationContainerRef,
|
||||
@@ -30,9 +32,6 @@ import type {
|
||||
|
||||
type State = NavigationState | PartialState<NavigationState> | undefined;
|
||||
|
||||
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.";
|
||||
|
||||
const serializableWarnings: string[] = [];
|
||||
const duplicateNameWarnings: string[] = [];
|
||||
|
||||
@@ -103,7 +102,7 @@ const BaseNavigationContainer = React.forwardRef(
|
||||
independent,
|
||||
children,
|
||||
}: NavigationContainerProps,
|
||||
ref?: React.Ref<NavigationContainerRef>
|
||||
ref?: React.Ref<NavigationContainerRef<ParamListBase>>
|
||||
) {
|
||||
const parent = React.useContext(NavigationStateContext);
|
||||
|
||||
@@ -202,16 +201,10 @@ const BaseNavigationContainer = React.forwardRef(
|
||||
const { addOptionsGetter, getCurrentOptions } = useOptionsGetters({});
|
||||
|
||||
React.useImperativeHandle(ref, () => ({
|
||||
...(Object.keys(
|
||||
CommonActions
|
||||
) as (keyof typeof CommonActions)[]).reduce<any>((acc, name) => {
|
||||
...Object.keys(CommonActions).reduce<any>((acc, name) => {
|
||||
acc[name] = (...args: any[]) =>
|
||||
dispatch(
|
||||
CommonActions[name](
|
||||
// @ts-expect-error: we can't know the type statically
|
||||
...args
|
||||
)
|
||||
);
|
||||
// @ts-expect-error: this is ok
|
||||
dispatch(CommonActions[name](...args));
|
||||
return acc;
|
||||
}, {}),
|
||||
...emitter.create('root'),
|
||||
@@ -223,6 +216,7 @@ const BaseNavigationContainer = React.forwardRef(
|
||||
getParent: () => undefined,
|
||||
getCurrentRoute,
|
||||
getCurrentOptions,
|
||||
isReady: () => listeners.focus[0] != null,
|
||||
}));
|
||||
|
||||
const onDispatchAction = React.useCallback(
|
||||
|
||||
@@ -3,16 +3,17 @@ import { act, render } from '@testing-library/react-native';
|
||||
import {
|
||||
DefaultRouterOptions,
|
||||
NavigationState,
|
||||
ParamListBase,
|
||||
Router,
|
||||
StackRouter,
|
||||
TabRouter,
|
||||
} from '@react-navigation/routers';
|
||||
import BaseNavigationContainer from '../BaseNavigationContainer';
|
||||
import NavigationStateContext from '../NavigationStateContext';
|
||||
import createNavigationContainerRef from '../createNavigationContainerRef';
|
||||
import MockRouter, { MockActions } from './__fixtures__/MockRouter';
|
||||
import useNavigationBuilder from '../useNavigationBuilder';
|
||||
import Screen from '../Screen';
|
||||
import type { NavigationContainerRef } from '../types';
|
||||
|
||||
it('throws when getState is accessed without a container', () => {
|
||||
expect.assertions(1);
|
||||
@@ -128,7 +129,7 @@ it('handle dispatching with ref', () => {
|
||||
);
|
||||
};
|
||||
|
||||
const ref = React.createRef<NavigationContainerRef>();
|
||||
const ref = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
const onStateChange = jest.fn();
|
||||
|
||||
@@ -226,7 +227,7 @@ it('handle resetting state with ref', () => {
|
||||
);
|
||||
};
|
||||
|
||||
const ref = React.createRef<NavigationContainerRef>();
|
||||
const ref = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
const onStateChange = jest.fn();
|
||||
|
||||
@@ -316,7 +317,7 @@ it('handles getRootState', () => {
|
||||
return descriptors[state.routes[state.index].key].render();
|
||||
};
|
||||
|
||||
const ref = React.createRef<NavigationContainerRef>();
|
||||
const ref = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
const element = (
|
||||
<BaseNavigationContainer ref={ref}>
|
||||
@@ -378,7 +379,7 @@ it('emits state events when the state changes', () => {
|
||||
);
|
||||
};
|
||||
|
||||
const ref = React.createRef<NavigationContainerRef>();
|
||||
const ref = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
const element = (
|
||||
<BaseNavigationContainer ref={ref}>
|
||||
@@ -448,7 +449,7 @@ it('emits state events when new navigator mounts', () => {
|
||||
);
|
||||
};
|
||||
|
||||
const ref = React.createRef<NavigationContainerRef>();
|
||||
const ref = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
const NestedNavigator = () => {
|
||||
const [isRendered, setIsRendered] = React.useState(false);
|
||||
@@ -537,7 +538,7 @@ it('emits option events when options change with tab router', () => {
|
||||
);
|
||||
};
|
||||
|
||||
const ref = React.createRef<NavigationContainerRef>();
|
||||
const ref = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
const element = (
|
||||
<BaseNavigationContainer ref={ref}>
|
||||
@@ -611,7 +612,7 @@ it('emits option events when options change with stack router', () => {
|
||||
);
|
||||
};
|
||||
|
||||
const ref = React.createRef<NavigationContainerRef>();
|
||||
const ref = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
const element = (
|
||||
<BaseNavigationContainer ref={ref}>
|
||||
@@ -677,7 +678,7 @@ it('emits option events when options change with stack router', () => {
|
||||
it('throws if there is no navigator rendered', () => {
|
||||
expect.assertions(1);
|
||||
|
||||
const ref = React.createRef<NavigationContainerRef>();
|
||||
const ref = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
const element = <BaseNavigationContainer ref={ref} children={null} />;
|
||||
|
||||
@@ -697,7 +698,7 @@ it('throws if there is no navigator rendered', () => {
|
||||
it("throws if the ref hasn't finished initializing", () => {
|
||||
expect.assertions(1);
|
||||
|
||||
const ref = React.createRef<NavigationContainerRef>();
|
||||
const ref = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
const TestNavigator = (props: any) => {
|
||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||
@@ -733,7 +734,7 @@ it("throws if the ref hasn't finished initializing", () => {
|
||||
});
|
||||
|
||||
it('invokes the unhandled action listener with the unhandled action', () => {
|
||||
const ref = React.createRef<NavigationContainerRef>();
|
||||
const ref = createNavigationContainerRef<ParamListBase>();
|
||||
const fn = jest.fn();
|
||||
|
||||
const TestNavigator = (props: any) => {
|
||||
@@ -779,7 +780,7 @@ it('works with state change events in independent nested container', () => {
|
||||
);
|
||||
};
|
||||
|
||||
const ref = React.createRef<NavigationContainerRef>();
|
||||
const ref = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
const onStateChange = jest.fn();
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import * as React from 'react';
|
||||
import { render, act } from '@testing-library/react-native';
|
||||
import type { NavigationState } from '@react-navigation/routers';
|
||||
import type { NavigationState, ParamListBase } from '@react-navigation/routers';
|
||||
import Screen from '../Screen';
|
||||
import BaseNavigationContainer from '../BaseNavigationContainer';
|
||||
import useNavigationBuilder from '../useNavigationBuilder';
|
||||
import createNavigationContainerRef from '../createNavigationContainerRef';
|
||||
import useNavigation from '../useNavigation';
|
||||
import MockRouter, { MockRouterKey } from './__fixtures__/MockRouter';
|
||||
import type { NavigationContainerRef } from '../types';
|
||||
|
||||
beforeEach(() => (MockRouterKey.current = 0));
|
||||
|
||||
@@ -679,7 +679,7 @@ it('navigates to nested child in a navigator', () => {
|
||||
|
||||
const onStateChange = jest.fn();
|
||||
|
||||
const navigation = React.createRef<NavigationContainerRef>();
|
||||
const navigation = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
const element = render(
|
||||
<BaseNavigationContainer ref={navigation} onStateChange={onStateChange}>
|
||||
@@ -715,7 +715,7 @@ it('navigates to nested child in a navigator', () => {
|
||||
expect(element).toMatchInlineSnapshot(`"[foo-a, undefined]"`);
|
||||
|
||||
act(() =>
|
||||
navigation.current?.navigate('bar', {
|
||||
navigation.navigate('bar', {
|
||||
screen: 'bar-b',
|
||||
params: { test: 42 },
|
||||
})
|
||||
@@ -726,7 +726,7 @@ it('navigates to nested child in a navigator', () => {
|
||||
);
|
||||
|
||||
act(() =>
|
||||
navigation.current?.navigate('bar', {
|
||||
navigation.navigate('bar', {
|
||||
screen: 'bar-a',
|
||||
params: { whoa: 'test' },
|
||||
})
|
||||
@@ -736,15 +736,15 @@ it('navigates to nested child in a navigator', () => {
|
||||
`"[bar-a, {\\"lol\\":\\"why\\",\\"whoa\\":\\"test\\"}]"`
|
||||
);
|
||||
|
||||
act(() => navigation.current?.navigate('bar', { screen: 'bar-b' }));
|
||||
act(() => navigation.navigate('bar', { screen: 'bar-b' }));
|
||||
|
||||
act(() => navigation.current?.goBack());
|
||||
act(() => navigation.goBack());
|
||||
|
||||
expect(element).toMatchInlineSnapshot(
|
||||
`"[bar-a, {\\"lol\\":\\"why\\",\\"whoa\\":\\"test\\"}]"`
|
||||
);
|
||||
|
||||
act(() => navigation.current?.navigate('bar', { screen: 'bar-b' }));
|
||||
act(() => navigation.navigate('bar', { screen: 'bar-b' }));
|
||||
|
||||
expect(element).toMatchInlineSnapshot(
|
||||
`"[bar-b, {\\"some\\":\\"stuff\\",\\"test\\":42,\\"whoa\\":\\"test\\"}]"`
|
||||
@@ -799,7 +799,7 @@ it('navigates to nested child in a navigator with initial: false', () => {
|
||||
|
||||
const onStateChange = jest.fn();
|
||||
|
||||
const navigation = React.createRef<NavigationContainerRef>();
|
||||
const navigation = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
const first = render(
|
||||
<BaseNavigationContainer ref={navigation} onStateChange={onStateChange}>
|
||||
@@ -833,7 +833,7 @@ it('navigates to nested child in a navigator with initial: false', () => {
|
||||
);
|
||||
|
||||
expect(first).toMatchInlineSnapshot(`"[foo-a, undefined]"`);
|
||||
expect(navigation.current?.getRootState()).toEqual({
|
||||
expect(navigation.getRootState()).toEqual({
|
||||
index: 0,
|
||||
key: '0',
|
||||
routeNames: ['foo', 'bar'],
|
||||
@@ -866,7 +866,7 @@ it('navigates to nested child in a navigator with initial: false', () => {
|
||||
});
|
||||
|
||||
act(() =>
|
||||
navigation.current?.navigate('bar', {
|
||||
navigation.navigate('bar', {
|
||||
screen: 'bar-b',
|
||||
params: { test: 42 },
|
||||
})
|
||||
@@ -876,7 +876,7 @@ it('navigates to nested child in a navigator with initial: false', () => {
|
||||
`"[bar-b, {\\"some\\":\\"stuff\\",\\"test\\":42}]"`
|
||||
);
|
||||
|
||||
expect(navigation.current?.getRootState()).toEqual({
|
||||
expect(navigation.getRootState()).toEqual({
|
||||
index: 2,
|
||||
key: '0',
|
||||
routeNames: ['foo', 'bar'],
|
||||
@@ -944,7 +944,7 @@ it('navigates to nested child in a navigator with initial: false', () => {
|
||||
);
|
||||
|
||||
expect(second).toMatchInlineSnapshot(`"[foo-a, undefined]"`);
|
||||
expect(navigation.current?.getRootState()).toEqual({
|
||||
expect(navigation.getRootState()).toEqual({
|
||||
index: 0,
|
||||
key: '4',
|
||||
routeNames: ['foo', 'bar'],
|
||||
@@ -971,7 +971,7 @@ it('navigates to nested child in a navigator with initial: false', () => {
|
||||
});
|
||||
|
||||
act(() =>
|
||||
navigation.current?.navigate('bar', {
|
||||
navigation.navigate('bar', {
|
||||
screen: 'bar-b',
|
||||
params: { test: 42 },
|
||||
initial: false,
|
||||
@@ -980,7 +980,7 @@ it('navigates to nested child in a navigator with initial: false', () => {
|
||||
|
||||
expect(second).toMatchInlineSnapshot(`"[bar-b, {\\"test\\":42}]"`);
|
||||
|
||||
expect(navigation.current?.getRootState()).toEqual({
|
||||
expect(navigation.getRootState()).toEqual({
|
||||
index: 2,
|
||||
key: '4',
|
||||
routeNames: ['foo', 'bar'],
|
||||
@@ -1071,7 +1071,7 @@ it('navigates to nested child in a navigator with initial: false', () => {
|
||||
|
||||
expect(third).toMatchInlineSnapshot(`"[bar-b, {\\"some\\":\\"stuff\\"}]"`);
|
||||
|
||||
expect(navigation.current?.getRootState()).toEqual({
|
||||
expect(navigation.getRootState()).toEqual({
|
||||
index: 1,
|
||||
key: '11',
|
||||
routeNames: ['foo', 'bar'],
|
||||
@@ -1119,7 +1119,7 @@ it('resets state of a nested child in a navigator', () => {
|
||||
|
||||
const onStateChange = jest.fn();
|
||||
|
||||
const navigation = React.createRef<NavigationContainerRef>();
|
||||
const navigation = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
const first = render(
|
||||
<BaseNavigationContainer ref={navigation} onStateChange={onStateChange}>
|
||||
@@ -1150,7 +1150,7 @@ it('resets state of a nested child in a navigator', () => {
|
||||
|
||||
expect(first).toMatchInlineSnapshot(`"[foo-a, undefined]"`);
|
||||
|
||||
expect(navigation.current?.getRootState()).toEqual({
|
||||
expect(navigation.getRootState()).toEqual({
|
||||
index: 0,
|
||||
key: '0',
|
||||
routeNames: ['foo', 'bar'],
|
||||
@@ -1183,7 +1183,7 @@ it('resets state of a nested child in a navigator', () => {
|
||||
});
|
||||
|
||||
act(() =>
|
||||
navigation.current?.navigate('bar', {
|
||||
navigation.navigate('bar', {
|
||||
state: {
|
||||
routes: [{ name: 'bar-a' }, { name: 'bar-b' }],
|
||||
},
|
||||
@@ -1192,7 +1192,7 @@ it('resets state of a nested child in a navigator', () => {
|
||||
|
||||
expect(first).toMatchInlineSnapshot(`"[bar-a, undefined]"`);
|
||||
|
||||
expect(navigation.current?.getRootState()).toEqual({
|
||||
expect(navigation.getRootState()).toEqual({
|
||||
index: 1,
|
||||
key: '0',
|
||||
routeNames: ['foo', 'bar'],
|
||||
@@ -1231,7 +1231,7 @@ it('resets state of a nested child in a navigator', () => {
|
||||
});
|
||||
|
||||
act(() =>
|
||||
navigation.current?.navigate('bar', {
|
||||
navigation.navigate('bar', {
|
||||
state: {
|
||||
index: 2,
|
||||
routes: [
|
||||
@@ -1245,7 +1245,7 @@ it('resets state of a nested child in a navigator', () => {
|
||||
|
||||
expect(first).toMatchInlineSnapshot(`"[bar-a, {\\"test\\":18}]"`);
|
||||
|
||||
expect(navigation.current?.getRootState()).toEqual({
|
||||
expect(navigation.getRootState()).toEqual({
|
||||
index: 1,
|
||||
key: '0',
|
||||
routeNames: ['foo', 'bar'],
|
||||
@@ -1336,7 +1336,7 @@ it('preserves order of screens in state with non-numeric names', () => {
|
||||
return null;
|
||||
};
|
||||
|
||||
const navigation = React.createRef<NavigationContainerRef>();
|
||||
const navigation = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
const root = (
|
||||
<BaseNavigationContainer ref={navigation}>
|
||||
@@ -1350,11 +1350,7 @@ it('preserves order of screens in state with non-numeric names', () => {
|
||||
|
||||
render(root);
|
||||
|
||||
expect(navigation.current?.getRootState().routeNames).toEqual([
|
||||
'foo',
|
||||
'bar',
|
||||
'baz',
|
||||
]);
|
||||
expect(navigation.getRootState().routeNames).toEqual(['foo', 'bar', 'baz']);
|
||||
});
|
||||
|
||||
it('preserves order of screens in state with numeric names', () => {
|
||||
@@ -1363,7 +1359,7 @@ it('preserves order of screens in state with numeric names', () => {
|
||||
return null;
|
||||
};
|
||||
|
||||
const navigation = React.createRef<NavigationContainerRef>();
|
||||
const navigation = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
const root = (
|
||||
<BaseNavigationContainer ref={navigation}>
|
||||
@@ -1377,11 +1373,7 @@ it('preserves order of screens in state with numeric names', () => {
|
||||
|
||||
render(root);
|
||||
|
||||
expect(navigation.current?.getRootState().routeNames).toEqual([
|
||||
'4',
|
||||
'7',
|
||||
'1',
|
||||
]);
|
||||
expect(navigation.getRootState().routeNames).toEqual(['4', '7', '1']);
|
||||
});
|
||||
|
||||
it("throws if navigator doesn't have any screens", () => {
|
||||
@@ -1801,7 +1793,7 @@ it('returns currently focused route with getCurrentRoute', () => {
|
||||
|
||||
const TestScreen = () => null;
|
||||
|
||||
const navigation = React.createRef<NavigationContainerRef>();
|
||||
const navigation = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
const container = (
|
||||
<BaseNavigationContainer ref={navigation}>
|
||||
@@ -1824,7 +1816,7 @@ it('returns currently focused route with getCurrentRoute', () => {
|
||||
|
||||
render(container).update(container);
|
||||
|
||||
expect(navigation.current?.getCurrentRoute()).toEqual({
|
||||
expect(navigation.getCurrentRoute()).toEqual({
|
||||
key: 'bar-a',
|
||||
name: 'bar-a',
|
||||
});
|
||||
@@ -1839,7 +1831,7 @@ it("returns focused screen's options with getCurrentOptions when focused screen
|
||||
|
||||
const TestScreen = () => null;
|
||||
|
||||
const navigation = React.createRef<NavigationContainerRef>();
|
||||
const navigation = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
const container = (
|
||||
<BaseNavigationContainer ref={navigation}>
|
||||
@@ -1870,14 +1862,14 @@ it("returns focused screen's options with getCurrentOptions when focused screen
|
||||
|
||||
render(container).update(container);
|
||||
|
||||
expect(navigation.current?.getCurrentOptions()).toEqual({
|
||||
expect(navigation.getCurrentOptions()).toEqual({
|
||||
sample: 'data',
|
||||
sample2: 'data',
|
||||
});
|
||||
|
||||
act(() => navigation.current?.navigate('bar-b'));
|
||||
act(() => navigation.navigate('bar-b'));
|
||||
|
||||
expect(navigation.current?.getCurrentOptions()).toEqual({
|
||||
expect(navigation.getCurrentOptions()).toEqual({
|
||||
sample2: 'data',
|
||||
sample3: 'data',
|
||||
});
|
||||
@@ -1892,7 +1884,7 @@ it("returns focused screen's options with getCurrentOptions when all screens are
|
||||
|
||||
const TestScreen = () => null;
|
||||
|
||||
const navigation = React.createRef<NavigationContainerRef>();
|
||||
const navigation = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
const container = (
|
||||
<BaseNavigationContainer ref={navigation}>
|
||||
@@ -1923,14 +1915,14 @@ it("returns focused screen's options with getCurrentOptions when all screens are
|
||||
|
||||
render(container).update(container);
|
||||
|
||||
expect(navigation.current?.getCurrentOptions()).toEqual({
|
||||
expect(navigation.getCurrentOptions()).toEqual({
|
||||
sample: 'data',
|
||||
sample2: 'data',
|
||||
});
|
||||
|
||||
act(() => navigation.current?.navigate('bar-b'));
|
||||
act(() => navigation.navigate('bar-b'));
|
||||
|
||||
expect(navigation.current?.getCurrentOptions()).toEqual({
|
||||
expect(navigation.getCurrentOptions()).toEqual({
|
||||
sample2: 'data',
|
||||
sample3: 'data',
|
||||
});
|
||||
@@ -1945,7 +1937,7 @@ it('does not throw if while getting current options with no options defined', ()
|
||||
|
||||
const TestScreen = () => null;
|
||||
|
||||
const navigation = React.createRef<NavigationContainerRef>();
|
||||
const navigation = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
const container = (
|
||||
<BaseNavigationContainer ref={navigation}>
|
||||
@@ -1968,11 +1960,11 @@ it('does not throw if while getting current options with no options defined', ()
|
||||
|
||||
render(container).update(container);
|
||||
|
||||
expect(navigation.current?.getCurrentOptions()).toEqual({});
|
||||
expect(navigation.getCurrentOptions()).toEqual({});
|
||||
});
|
||||
|
||||
it('does not throw if while getting current options with empty container', () => {
|
||||
const navigation = React.createRef<NavigationContainerRef>();
|
||||
const navigation = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
const container = (
|
||||
<BaseNavigationContainer ref={navigation} children={null} />
|
||||
@@ -1980,5 +1972,5 @@ it('does not throw if while getting current options with empty container', () =>
|
||||
|
||||
render(container).update(container);
|
||||
|
||||
expect(navigation.current?.getCurrentOptions()).toEqual(undefined);
|
||||
expect(navigation.getCurrentOptions()).toEqual(undefined);
|
||||
});
|
||||
|
||||
@@ -5,15 +5,16 @@ import {
|
||||
DefaultRouterOptions,
|
||||
NavigationState,
|
||||
StackRouter,
|
||||
ParamListBase,
|
||||
} from '@react-navigation/routers';
|
||||
import useNavigationBuilder from '../useNavigationBuilder';
|
||||
import BaseNavigationContainer from '../BaseNavigationContainer';
|
||||
import Screen from '../Screen';
|
||||
import createNavigationContainerRef from '../createNavigationContainerRef';
|
||||
import MockRouter, {
|
||||
MockActions,
|
||||
MockRouterKey,
|
||||
} from './__fixtures__/MockRouter';
|
||||
import type { NavigationContainerRef } from '../types';
|
||||
|
||||
jest.mock('nanoid/non-secure', () => {
|
||||
const m = { nanoid: () => String(++m.__key), __key: 0 };
|
||||
@@ -571,7 +572,7 @@ it("prevents removing a screen with 'beforeRemove' event", () => {
|
||||
|
||||
const onStateChange = jest.fn();
|
||||
|
||||
const ref = React.createRef<NavigationContainerRef>();
|
||||
const ref = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
const element = (
|
||||
<BaseNavigationContainer ref={ref} onStateChange={onStateChange}>
|
||||
@@ -706,7 +707,7 @@ it("prevents removing a child screen with 'beforeRemove' event", () => {
|
||||
|
||||
const onStateChange = jest.fn();
|
||||
|
||||
const ref = React.createRef<NavigationContainerRef>();
|
||||
const ref = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
const element = (
|
||||
<BaseNavigationContainer ref={ref} onStateChange={onStateChange}>
|
||||
@@ -867,7 +868,7 @@ it("prevents removing a grand child screen with 'beforeRemove' event", () => {
|
||||
|
||||
const onStateChange = jest.fn();
|
||||
|
||||
const ref = React.createRef<NavigationContainerRef>();
|
||||
const ref = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
const element = (
|
||||
<BaseNavigationContainer ref={ref} onStateChange={onStateChange}>
|
||||
@@ -1065,7 +1066,7 @@ it("prevents removing by multiple screens with 'beforeRemove' event", () => {
|
||||
|
||||
const onStateChange = jest.fn();
|
||||
|
||||
const ref = React.createRef<NavigationContainerRef>();
|
||||
const ref = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
const element = (
|
||||
<BaseNavigationContainer ref={ref} onStateChange={onStateChange}>
|
||||
@@ -1217,7 +1218,7 @@ it("prevents removing a child screen with 'beforeRemove' event with 'resetRoot'"
|
||||
|
||||
const onStateChange = jest.fn();
|
||||
|
||||
const ref = React.createRef<NavigationContainerRef>();
|
||||
const ref = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
const element = (
|
||||
<BaseNavigationContainer ref={ref} onStateChange={onStateChange}>
|
||||
|
||||
47
packages/core/src/createNavigationContainerRef.tsx
Normal file
47
packages/core/src/createNavigationContainerRef.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import { CommonActions, ParamListBase } from '@react-navigation/routers';
|
||||
import type { 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.";
|
||||
|
||||
export default function createNavigationContainerRef<
|
||||
ParamList extends ParamListBase
|
||||
>(): NavigationContainerRefWithCurrent<ParamList> {
|
||||
const methods = [
|
||||
...Object.keys(CommonActions),
|
||||
'addListener',
|
||||
'removeListener',
|
||||
'resetRoot',
|
||||
'dispatch',
|
||||
'canGoBack',
|
||||
'getRootState',
|
||||
'getState',
|
||||
'getParent',
|
||||
'getCurrentRoute',
|
||||
'getCurrentOptions',
|
||||
] as const;
|
||||
|
||||
const ref: NavigationContainerRefWithCurrent<ParamList> = {
|
||||
...methods.reduce<any>((acc, name) => {
|
||||
acc[name] = (...args: any[]) => {
|
||||
if (ref.current == null) {
|
||||
console.error(NOT_INITIALIZED_ERROR);
|
||||
} else {
|
||||
// @ts-expect-error: this is ok
|
||||
return ref.current[name](...args);
|
||||
}
|
||||
};
|
||||
return acc;
|
||||
}, {}),
|
||||
isReady: () => {
|
||||
if (ref.current == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ref.current.isReady();
|
||||
},
|
||||
current: null,
|
||||
};
|
||||
|
||||
return ref;
|
||||
}
|
||||
@@ -3,6 +3,9 @@ export * from '@react-navigation/routers';
|
||||
export { default as BaseNavigationContainer } from './BaseNavigationContainer';
|
||||
export { default as createNavigatorFactory } from './createNavigatorFactory';
|
||||
|
||||
export { default as createNavigationContainerRef } from './createNavigationContainerRef';
|
||||
export { default as useNavigationContainerRef } from './useNavigationContainerRef';
|
||||
|
||||
export { default as NavigationHelpersContext } from './NavigationHelpersContext';
|
||||
export { default as NavigationContext } from './NavigationContext';
|
||||
export { default as NavigationRouteContext } from './NavigationRouteContext';
|
||||
|
||||
@@ -485,7 +485,9 @@ export type NavigationContainerEventMap = {
|
||||
};
|
||||
};
|
||||
|
||||
export type NavigationContainerRef = NavigationHelpers<ParamListBase> &
|
||||
export type NavigationContainerRef<
|
||||
ParamList extends ParamListBase
|
||||
> = NavigationHelpers<ParamList> &
|
||||
EventConsumer<NavigationContainerEventMap> & {
|
||||
/**
|
||||
* Reset the navigation state of the root navigator to the provided state.
|
||||
@@ -505,8 +507,18 @@ export type NavigationContainerRef = NavigationHelpers<ParamListBase> &
|
||||
* Get the currently focused route's options.
|
||||
*/
|
||||
getCurrentOptions(): object | undefined;
|
||||
/**
|
||||
* Whether the navigation container is ready to handle actions.
|
||||
*/
|
||||
isReady(): boolean;
|
||||
};
|
||||
|
||||
export type NavigationContainerRefWithCurrent<
|
||||
ParamList extends ParamListBase
|
||||
> = NavigationContainerRef<ParamList> & {
|
||||
current: NavigationContainerRef<ParamList> | null;
|
||||
};
|
||||
|
||||
export type TypedNavigator<
|
||||
ParamList extends ParamListBase,
|
||||
State extends NavigationState,
|
||||
|
||||
18
packages/core/src/useNavigationContainerRef.tsx
Normal file
18
packages/core/src/useNavigationContainerRef.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import * as React from 'react';
|
||||
import type { ParamListBase } from '@react-navigation/routers';
|
||||
import createNavigationContainerRef from './createNavigationContainerRef';
|
||||
import type { NavigationContainerRefWithCurrent } from './types';
|
||||
|
||||
export default function useNavigationContainerRef<
|
||||
ParamList extends ParamListBase
|
||||
>(): NavigationContainerRefWithCurrent<ParamList> {
|
||||
const navigation = React.useRef<NavigationContainerRefWithCurrent<ParamList> | null>(
|
||||
null
|
||||
);
|
||||
|
||||
if (navigation.current == null) {
|
||||
navigation.current = createNavigationContainerRef<ParamList>();
|
||||
}
|
||||
|
||||
return navigation.current;
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import type {
|
||||
NavigationContainerRef,
|
||||
NavigationState,
|
||||
NavigationAction,
|
||||
ParamListBase,
|
||||
} from '@react-navigation/core';
|
||||
import deepEqual from 'deep-equal';
|
||||
|
||||
@@ -22,7 +23,7 @@ type DevToolsExtension = {
|
||||
declare const __REDUX_DEVTOOLS_EXTENSION__: DevToolsExtension | undefined;
|
||||
|
||||
export default function useReduxDevToolsExtension(
|
||||
ref: React.RefObject<NavigationContainerRef>
|
||||
ref: React.RefObject<NavigationContainerRef<ParamListBase>>
|
||||
) {
|
||||
const devToolsRef = React.useRef<DevToolsConnection>();
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
BaseNavigationContainer,
|
||||
NavigationContainerProps,
|
||||
NavigationContainerRef,
|
||||
ParamListBase,
|
||||
} from '@react-navigation/core';
|
||||
import ThemeProvider from './theming/ThemeProvider';
|
||||
import DefaultTheme from './theming/DefaultTheme';
|
||||
@@ -44,11 +45,13 @@ const NavigationContainer = React.forwardRef(function NavigationContainer(
|
||||
onReady,
|
||||
...rest
|
||||
}: Props,
|
||||
ref?: React.Ref<NavigationContainerRef | null>
|
||||
ref?: React.Ref<NavigationContainerRef<ParamListBase> | null>
|
||||
) {
|
||||
const isLinkingEnabled = linking ? linking.enabled !== false : false;
|
||||
|
||||
const refContainer = React.useRef<NavigationContainerRef>(null);
|
||||
const refContainer = React.useRef<NavigationContainerRef<ParamListBase>>(
|
||||
null
|
||||
);
|
||||
|
||||
useBackButton(refContainer);
|
||||
useDocumentTitle(refContainer, documentTitle);
|
||||
|
||||
@@ -5,7 +5,8 @@ import {
|
||||
StackRouter,
|
||||
TabRouter,
|
||||
NavigationHelpersContext,
|
||||
NavigationContainerRef,
|
||||
createNavigationContainerRef,
|
||||
ParamListBase,
|
||||
} from '@react-navigation/core';
|
||||
import { act, render } from '@testing-library/react-native';
|
||||
import NavigationContainer from '../NavigationContainer';
|
||||
@@ -79,7 +80,7 @@ it('integrates with the history API', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const navigation = React.createRef<NavigationContainerRef>();
|
||||
const navigation = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
render(
|
||||
<NavigationContainer ref={navigation} linking={linking}>
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import * as React from 'react';
|
||||
import { render, RenderAPI } from '@testing-library/react-native';
|
||||
import type { NavigationContainerRef } from '@react-navigation/core';
|
||||
import {
|
||||
createNavigationContainerRef,
|
||||
ParamListBase,
|
||||
} from '@react-navigation/core';
|
||||
import useLinking from '../useLinking';
|
||||
|
||||
it('throws if multiple instances of useLinking are used', () => {
|
||||
const ref = React.createRef<NavigationContainerRef>();
|
||||
const ref = createNavigationContainerRef<ParamListBase>();
|
||||
|
||||
const options = { prefixes: [] };
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import * as React from 'react';
|
||||
import { BackHandler } from 'react-native';
|
||||
import type { NavigationContainerRef } from '@react-navigation/core';
|
||||
import type {
|
||||
NavigationContainerRef,
|
||||
ParamListBase,
|
||||
} from '@react-navigation/core';
|
||||
|
||||
export default function useBackButton(
|
||||
ref: React.RefObject<NavigationContainerRef>
|
||||
ref: React.RefObject<NavigationContainerRef<ParamListBase>>
|
||||
) {
|
||||
React.useEffect(() => {
|
||||
const subscription = BackHandler.addEventListener(
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import * as React from 'react';
|
||||
import type { NavigationContainerRef } from '@react-navigation/core';
|
||||
import type {
|
||||
NavigationContainerRef,
|
||||
ParamListBase,
|
||||
} from '@react-navigation/core';
|
||||
import type { DocumentTitleOptions } from './types';
|
||||
|
||||
/**
|
||||
* Set the document title for the active screen
|
||||
*/
|
||||
export default function useDocumentTitle(
|
||||
ref: React.RefObject<NavigationContainerRef>,
|
||||
ref: React.RefObject<NavigationContainerRef<ParamListBase>>,
|
||||
{
|
||||
enabled = true,
|
||||
formatter = (options, route) => options?.title ?? route?.name,
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
getActionFromState,
|
||||
getStateFromPath as getStateFromPathDefault,
|
||||
NavigationContainerRef,
|
||||
ParamListBase,
|
||||
} from '@react-navigation/core';
|
||||
import extractPathFromURL from './extractPathFromURL';
|
||||
import type { LinkingOptions } from './types';
|
||||
@@ -13,7 +14,7 @@ type ResultState = ReturnType<typeof getStateFromPathDefault>;
|
||||
let isUsingLinking = false;
|
||||
|
||||
export default function useLinking(
|
||||
ref: React.RefObject<NavigationContainerRef>,
|
||||
ref: React.RefObject<NavigationContainerRef<ParamListBase>>,
|
||||
{
|
||||
enabled = true,
|
||||
prefixes,
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
NavigationState,
|
||||
getActionFromState,
|
||||
findFocusedRoute,
|
||||
ParamListBase,
|
||||
} from '@react-navigation/core';
|
||||
import { nanoid } from 'nanoid/non-secure';
|
||||
import ServerContext from './ServerContext';
|
||||
@@ -288,7 +289,7 @@ const series = (cb: () => Promise<void>) => {
|
||||
let isUsingLinking = false;
|
||||
|
||||
export default function useLinking(
|
||||
ref: React.RefObject<NavigationContainerRef>,
|
||||
ref: React.RefObject<NavigationContainerRef<ParamListBase>>,
|
||||
{
|
||||
enabled = true,
|
||||
config,
|
||||
|
||||
Reference in New Issue
Block a user