Compare commits

...

7 Commits

Author SHA1 Message Date
satyajit.happy
06b3e6bda7 chore: publish
- @react-navigation/core@5.0.0-alpha.7
 - @react-navigation/example@5.0.0-alpha.6
 - @react-navigation/native@5.0.0-alpha.7
2019-08-31 23:23:10 +02:00
satyajit.happy
3c840bbae3 fix: fix navigation object changing too often 2019-08-31 23:21:27 +02:00
satyajit.happy
d4ad9d48f9 refactor: make descriptor.options a getter 2019-08-31 23:10:30 +02:00
satyajit.happy
a0e9784d98 chore: publish
- @react-navigation/core@5.0.0-alpha.6
 - @react-navigation/example@5.0.0-alpha.5
 - @react-navigation/native@5.0.0-alpha.6
2019-08-31 23:01:28 +02:00
satyajit.happy
eff0c0464f feat: support function in screenOptions 2019-08-31 23:00:27 +02:00
satyajit.happy
fe9ba2bf71 refactor: rename NativeContainer -> NavigationNativeContainer 2019-08-31 21:02:48 +02:00
Michał Osadnik
b0a3756d18 feat: add useRoute (#89) 2019-08-31 15:56:04 +01:00
21 changed files with 529 additions and 218 deletions

View File

@@ -3,6 +3,35 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [5.0.0-alpha.7](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.5...@react-navigation/core@5.0.0-alpha.7) (2019-08-31)
### Bug Fixes
* fix navigation object changing too often ([3c840bb](https://github.com/react-navigation/navigation-ex/commit/3c840bb))
### Features
* add useRoute ([#89](https://github.com/react-navigation/navigation-ex/issues/89)) ([b0a3756](https://github.com/react-navigation/navigation-ex/commit/b0a3756))
* support function in screenOptions ([eff0c04](https://github.com/react-navigation/navigation-ex/commit/eff0c04))
# [5.0.0-alpha.6](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.5...@react-navigation/core@5.0.0-alpha.6) (2019-08-31)
### Features
* add useRoute ([#89](https://github.com/react-navigation/navigation-ex/issues/89)) ([b0a3756](https://github.com/react-navigation/navigation-ex/commit/b0a3756))
* support function in screenOptions ([eff0c04](https://github.com/react-navigation/navigation-ex/commit/eff0c04))
# [5.0.0-alpha.5](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.4...@react-navigation/core@5.0.0-alpha.5) (2019-08-30)
**Note:** Version bump only for package @react-navigation/core

View File

@@ -6,7 +6,7 @@
"react-native",
"react-navigation"
],
"version": "5.0.0-alpha.5",
"version": "5.0.0-alpha.7",
"license": "MIT",
"repository": {
"type": "git",

View File

@@ -1,11 +1,22 @@
import * as React from 'react';
import { NavigationProp, ParamListBase } from './types';
import {
NavigationProp,
ParamListBase,
PartialState,
Route,
NavigationState,
} from './types';
/**
* Context which holds the navigation prop for a screen.
*/
const NavigationContext = React.createContext<
NavigationProp<ParamListBase, string, any, any> | undefined
>(undefined);
const NavigationContext = React.createContext<{
navigation: NavigationProp<ParamListBase, string, any, any> | undefined;
route:
| Route<string> & {
state?: NavigationState | PartialState<NavigationState>;
}
| undefined;
}>({ navigation: undefined, route: undefined });
export default NavigationContext;

View File

@@ -56,7 +56,7 @@ export default function SceneView<
),
});
},
[getState, route, setState]
[getState, route.key, setState]
);
const context = React.useMemo(
@@ -76,8 +76,16 @@ export default function SceneView<
]
);
const navigationContext = React.useMemo(
() => ({
navigation,
route,
}),
[navigation, route]
);
return (
<NavigationContext.Provider value={navigation}>
<NavigationContext.Provider value={navigationContext}>
<NavigationStateContext.Provider value={context}>
<EnsureSingleNavigator>
<StaticContainer

View File

@@ -46,11 +46,320 @@ it('sets options with options prop as an object', () => {
</NavigationContainer>
);
expect(root).toMatchInlineSnapshot(`
<main>
<h1>
Hello world
</h1>
<div>
Test screen
</div>
</main>
`);
});
it('sets options with options prop as a fuction', () => {
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 TestScreen = (): any => 'Test screen';
const root = render(
<NavigationContainer>
<TestNavigator>
<Screen
name="foo"
component={TestScreen}
options={({ route }: any) => ({ title: route.params.author })}
initialParams={{ author: 'Jane' }}
/>
<Screen name="bar" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
);
expect(root).toMatchInlineSnapshot(`
<main>
<h1>
Jane
</h1>
<div>
Test screen
</div>
</main>
`);
});
it('sets options with screenOptions prop as an object', () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder<
NavigationState,
any,
{ title?: string },
any
>(MockRouter, props);
return (
<>
{state.routes.map(route => {
const { render, options } = descriptors[route.key];
return (
<main key={route.key}>
<h1>{options.title}</h1>
<div>{render()}</div>
</main>
);
})}
</>
);
};
const TestScreenA = (): any => 'Test screen A';
const TestScreenB = (): any => 'Test screen B';
const root = render(
<NavigationContainer>
<TestNavigator screenOptions={{ title: 'Hello world' }}>
<Screen name="foo" component={TestScreenA} />
<Screen name="bar" component={TestScreenB} />
</TestNavigator>
</NavigationContainer>
);
expect(root).toMatchInlineSnapshot(`
Array [
<main>
<h1>
Hello world
</h1>
<div>
Test screen A
</div>
</main>,
<main>
<h1>
Hello world
</h1>
<div>
Test screen B
</div>
</main>,
]
`);
});
it('sets options with screenOptions prop as a fuction', () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder<
NavigationState,
any,
{ title?: string },
any
>(MockRouter, props);
return (
<>
{state.routes.map(route => {
const { render, options } = descriptors[route.key];
return (
<main key={route.key}>
<h1>{options.title}</h1>
<div>{render()}</div>
</main>
);
})}
</>
);
};
const TestScreenA = (): any => 'Test screen A';
const TestScreenB = (): any => 'Test screen B';
const root = render(
<NavigationContainer>
<TestNavigator
screenOptions={({ route }: any) => ({
title: `${route.name}: ${route.params.author || route.params.fruit}`,
})}
>
<Screen
name="foo"
component={TestScreenA}
initialParams={{ author: 'Jane' }}
/>
<Screen
name="bar"
component={TestScreenB}
initialParams={{ fruit: 'Apple' }}
/>
</TestNavigator>
</NavigationContainer>
);
expect(root).toMatchInlineSnapshot(`
Array [
<main>
<h1>
foo: Jane
</h1>
<div>
Test screen A
</div>
</main>,
<main>
<h1>
bar: Apple
</h1>
<div>
Test screen B
</div>
</main>,
]
`);
});
it('sets initial options with setOptions', () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder<
NavigationState,
any,
{
title?: string;
color?: string;
},
any
>(MockRouter, props);
const { render, options } = descriptors[state.routes[state.index].key];
return (
<main>
<h1 color={options.color}>{options.title}</h1>
<div>{render()}</div>
</main>
);
};
const TestScreen = ({ navigation }: any): any => {
navigation.setOptions({
title: 'Hello world',
});
return 'Test screen';
};
const root = render(
<NavigationContainer>
<TestNavigator>
<Screen name="foo" options={{ color: 'blue' }}>
{props => <TestScreen {...props} />}
</Screen>
<Screen name="bar" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
);
expect(root).toMatchInlineSnapshot(`
<main>
<h1
color="blue"
>
Hello world
</h1>
<div>
Test screen
</div>
</main>
`);
});
it('updates options with setOptions', () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder<
NavigationState,
any,
any,
any
>(MockRouter, props);
const { render, options } = descriptors[state.routes[state.index].key];
return (
<main>
<h1 color={options.color}>{options.title}</h1>
<p>{options.description}</p>
<caption>{options.author}</caption>
<div>{render()}</div>
</main>
);
};
const TestScreen = ({ navigation }: any): any => {
navigation.setOptions({
title: 'Hello world',
description: 'Something here',
});
React.useEffect(() => {
const timer = setTimeout(() =>
navigation.setOptions({
title: 'Hello again',
author: 'Jane',
})
);
return () => clearTimeout(timer);
});
return 'Test screen';
};
const element = (
<NavigationContainer>
<TestNavigator>
<Screen name="foo" options={{ color: 'blue' }}>
{props => <TestScreen {...props} />}
</Screen>
<Screen name="bar" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
);
const root = render(element);
act(() => jest.runAllTimers());
root.update(element);
expect(root).toMatchInlineSnapshot(`
<main>
<h1>
Hello world
<h1
color="blue"
>
Hello again
</h1>
<p>
Something here
</p>
<caption>
Jane
</caption>
<div>
Test screen
</div>
@@ -295,180 +604,3 @@ it('returns true for canGoBack when parent router handles GO_BACK', () => {
expect(result).toBe(false);
});
it('sets options with options prop as a fuction', () => {
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 TestScreen = (): any => 'Test screen';
const root = render(
<NavigationContainer>
<TestNavigator>
<Screen
name="foo"
component={TestScreen}
options={({ route }: any) => ({ title: route.params.author })}
initialParams={{ author: 'Jane' }}
/>
<Screen name="bar" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
);
expect(root).toMatchInlineSnapshot(`
<main>
<h1>
Jane
</h1>
<div>
Test screen
</div>
</main>
`);
});
it('sets initial options with setOptions', () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder<
NavigationState,
any,
{
title?: string;
color?: string;
},
any
>(MockRouter, props);
const { render, options } = descriptors[state.routes[state.index].key];
return (
<main>
<h1 color={options.color}>{options.title}</h1>
<div>{render()}</div>
</main>
);
};
const TestScreen = ({ navigation }: any): any => {
navigation.setOptions({
title: 'Hello world',
});
return 'Test screen';
};
const root = render(
<NavigationContainer>
<TestNavigator>
<Screen name="foo" options={{ color: 'blue' }}>
{props => <TestScreen {...props} />}
</Screen>
<Screen name="bar" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
);
expect(root).toMatchInlineSnapshot(`
<main>
<h1
color="blue"
>
Hello world
</h1>
<div>
Test screen
</div>
</main>
`);
});
it('updates options with setOptions', () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder<
NavigationState,
any,
any,
any
>(MockRouter, props);
const { render, options } = descriptors[state.routes[state.index].key];
return (
<main>
<h1 color={options.color}>{options.title}</h1>
<p>{options.description}</p>
<caption>{options.author}</caption>
<div>{render()}</div>
</main>
);
};
const TestScreen = ({ navigation }: any): any => {
navigation.setOptions({
title: 'Hello world',
description: 'Something here',
});
React.useEffect(() => {
const timer = setTimeout(() =>
navigation.setOptions({
title: 'Hello again',
author: 'Jane',
})
);
return () => clearTimeout(timer);
});
return 'Test screen';
};
const element = (
<NavigationContainer>
<TestNavigator>
<Screen name="foo" options={{ color: 'blue' }}>
{props => <TestScreen {...props} />}
</Screen>
<Screen name="bar" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
);
const root = render(element);
act(() => jest.runAllTimers());
root.update(element);
expect(root).toMatchInlineSnapshot(`
<main>
<h1
color="blue"
>
Hello again
</h1>
<p>
Something here
</p>
<caption>
Jane
</caption>
<div>
Test screen
</div>
</main>
`);
});

View File

@@ -0,0 +1,34 @@
import * as React from 'react';
import { render } from 'react-native-testing-library';
import useNavigationBuilder from '../useNavigationBuilder';
import useRoute from '../useRoute';
import NavigationContainer from '../NavigationContainer';
import Screen from '../Screen';
import MockRouter from './__fixtures__/MockRouter';
import { RouteProp } from '../types';
it('gets route prop from context', () => {
expect.assertions(1);
const TestNavigator = (props: any): any => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return state.routes.map(route => descriptors[route.key].render());
};
const Test = () => {
const route = useRoute<RouteProp<{ sample: { x: string } }, 'sample'>>();
expect(route && route.params && route.params.x).toEqual(1);
return null;
};
render(
<NavigationContainer>
<TestNavigator>
<Screen name="foo" component={Test} initialParams={{ x: 1 }} />
</TestNavigator>
</NavigationContainer>
);
});

View File

@@ -10,6 +10,7 @@ export { default as NavigationContext } from './NavigationContext';
export { default as useNavigationBuilder } from './useNavigationBuilder';
export { default as useNavigation } from './useNavigation';
export { default as useRoute } from './useRoute';
export { default as useFocusEffect } from './useFocusEffect';
export { default as useIsFocused } from './useIsFocused';

View File

@@ -96,7 +96,12 @@ export type DefaultNavigatorOptions<
/**
* Default options for all screens under this navigator.
*/
screenOptions?: ScreenOptions;
screenOptions?:
| ScreenOptions
| ((props: {
route: RouteProp<ParamListBase, string>;
navigation: any;
}) => ScreenOptions);
};
export type RouterFactory<
@@ -534,11 +539,24 @@ export type TypedNavigator<
* Navigator component which manages the child screens.
*/
Navigator: React.ComponentType<
React.ComponentProps<Navigator> & {
Omit<
React.ComponentProps<Navigator>,
'initialRouteName' | 'screenOptions'
> & {
/**
* Route to focus on initial render.
* Name of the route to focus by on initial render.
* If not specified, usually the first route is used.
*/
initialRouteName?: keyof ParamList;
/**
* Default options for all screens under this navigator.
*/
screenOptions?:
| ScreenOptions
| ((props: {
route: RouteProp<ParamList, keyof ParamList>;
navigation: any;
}) => ScreenOptions);
}
>;
/**

View File

@@ -13,6 +13,7 @@ import {
NavigationState,
ParamListBase,
RouteConfig,
RouteProp,
Router,
} from './types';
@@ -20,7 +21,12 @@ type Options<ScreenOptions extends object> = {
state: NavigationState;
screens: { [key: string]: RouteConfig<ParamListBase, string, ScreenOptions> };
navigation: NavigationHelpers<ParamListBase>;
screenOptions?: ScreenOptions;
screenOptions?:
| ScreenOptions
| ((props: {
route: RouteProp<ParamListBase, string>;
navigation: any;
}) => ScreenOptions);
onAction: (
action: NavigationAction,
visitedNavigators?: Set<string>
@@ -93,13 +99,15 @@ export default function useDescriptors<
return state.routes.reduce(
(acc, route) => {
const screen = screens[route.name];
const navigation = navigations[route.key];
acc[route.key] = {
navigation,
render() {
return (
<NavigationBuilderContext.Provider key={route.key} value={context}>
<SceneView
navigation={navigations[route.key]}
navigation={navigation}
route={route}
screen={screen}
getState={getState}
@@ -108,22 +116,30 @@ export default function useDescriptors<
</NavigationBuilderContext.Provider>
);
},
options: {
// The default `screenOptions` passed to the navigator
...screenOptions,
// The `options` prop passed to `Screen` elements
...(typeof screen.options === 'object' || screen.options == null
? screen.options
: screen.options({
// @ts-ignore
route,
navigation: navigations[route.key],
})),
// The options set via `navigation.setOptions`
...options[route.key],
get options() {
return {
// The default `screenOptions` passed to the navigator
...(typeof screenOptions === 'object' || screenOptions == null
? screenOptions
: screenOptions({
// @ts-ignore
route,
navigation,
})),
// The `options` prop passed to `Screen` elements
...(typeof screen.options === 'object' || screen.options == null
? screen.options
: screen.options({
// @ts-ignore
route,
navigation,
})),
// The options set via `navigation.setOptions`
...options[route.key],
};
},
navigation: navigations[route.key],
};
return acc;
},
{} as {

View File

@@ -12,7 +12,7 @@ type Options = {
* Hook to take care of emitting `focus` and `blur` events.
*/
export default function useFocusEvents({ state, emitter }: Options) {
const navigation = React.useContext(NavigationContext);
const { navigation } = React.useContext(NavigationContext);
const lastFocusedKeyRef = React.useRef<string | undefined>();
const currentFocusedKey = state.routes[state.index].key;

View File

@@ -10,7 +10,7 @@ import { NavigationProp, ParamListBase } from './types';
export default function useNavigation<
T extends NavigationProp<ParamListBase>
>(): T {
const navigation = React.useContext(NavigationContext);
const { navigation } = React.useContext(NavigationContext);
if (navigation === undefined) {
throw new Error(

View File

@@ -43,7 +43,7 @@ export default function useNavigationCache<
// Cache object which holds navigation objects for each screen
// We use `React.useMemo` instead of `React.useRef` coz we want to invalidate it when deps change
// In reality, these deps will rarely change, if ever
const parentNavigation = React.useContext(NavigationContext);
const { navigation: parentNavigation } = React.useContext(NavigationContext);
const cache = React.useMemo(
() => ({ current: {} as NavigationCache<State, ScreenOptions> }),

View File

@@ -37,7 +37,9 @@ export default function useNavigationHelpers<
Action extends NavigationAction,
EventMap extends { [key: string]: any }
>({ onAction, getState, setState, emitter, router }: Options<State, Action>) {
const parentNavigationHelpers = React.useContext(NavigationContext);
const { navigation: parentNavigationHelpers } = React.useContext(
NavigationContext
);
const { performTransaction } = React.useContext(NavigationStateContext);
return React.useMemo(() => {

View File

@@ -0,0 +1,22 @@
import * as React from 'react';
import NavigationContext from './NavigationContext';
import { ParamListBase, RouteProp } from './types';
/**
* Hook to access the route prop of the parent screen anywhere.
*
* @returns Route prop of the parent screen.
*/
export default function useRoute<
T extends RouteProp<ParamListBase, string>
>(): T {
const { route } = React.useContext(NavigationContext);
if (route === undefined) {
throw new Error(
"We couldn't find a route object. Is your component inside a navigator?"
);
}
return route as T;
}

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [5.0.0-alpha.6](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.4...@react-navigation/example@5.0.0-alpha.6) (2019-08-31)
**Note:** Version bump only for package @react-navigation/example
# [5.0.0-alpha.5](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.4...@react-navigation/example@5.0.0-alpha.5) (2019-08-31)
**Note:** Version bump only for package @react-navigation/example
# [5.0.0-alpha.4](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.3...@react-navigation/example@5.0.0-alpha.4) (2019-08-31)

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/example",
"description": "Demo app to showcase various functionality of React Navigation",
"version": "5.0.0-alpha.4",
"version": "5.0.0-alpha.6",
"private": true,
"workspaces": {
"nohoist": [

View File

@@ -8,7 +8,10 @@ import {
getStateFromPath,
NavigationContainerRef,
} from '@react-navigation/core';
import { useLinking, NativeContainer } from '@react-navigation/native';
import {
useLinking,
NavigationNativeContainer,
} from '@react-navigation/native';
import {
createDrawerNavigator,
DrawerNavigationProp,
@@ -118,7 +121,7 @@ export default function App() {
}
return (
<NativeContainer
<NavigationNativeContainer
ref={containerRef}
initialState={initialState}
onStateChange={state =>
@@ -177,6 +180,6 @@ export default function App() {
)}
</Drawer.Screen>
</Drawer.Navigator>
</NativeContainer>
</NavigationNativeContainer>
);
}

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [5.0.0-alpha.7](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/native@5.0.0-alpha.5...@react-navigation/native@5.0.0-alpha.7) (2019-08-31)
**Note:** Version bump only for package @react-navigation/native
# [5.0.0-alpha.6](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/native@5.0.0-alpha.5...@react-navigation/native@5.0.0-alpha.6) (2019-08-31)
**Note:** Version bump only for package @react-navigation/native
# [5.0.0-alpha.5](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/native@5.0.0-alpha.4...@react-navigation/native@5.0.0-alpha.5) (2019-08-29)

View File

@@ -7,7 +7,7 @@
"ios",
"android"
],
"version": "5.0.0-alpha.5",
"version": "5.0.0-alpha.7",
"license": "MIT",
"repository": {
"type": "git",

View File

@@ -16,14 +16,14 @@ import useBackButton from './useBackButton';
* @param props.children Child elements to render the content.
* @param props.ref Ref object which refers to the navigation object containing helper methods.
*/
const NativeContainer = React.forwardRef(function NativeContainer(
const NavigationNativeContainer = React.forwardRef(function NativeContainer(
props: NavigationContainerProps,
ref: React.Ref<NavigationContainerRef>
) {
const refContainer = React.useRef<NavigationContainerRef>(null);
useBackButton(refContainer);
React.useImperativeHandle(ref, () => refContainer.current);
return (
@@ -35,4 +35,4 @@ const NativeContainer = React.forwardRef(function NativeContainer(
);
});
export default NativeContainer;
export default NavigationNativeContainer;

View File

@@ -1,4 +1,7 @@
export { default as NativeContainer } from './NativeContainer';
export {
default as NavigationNativeContainer,
} from './NavigationNativeContainer';
export { default as useBackButton } from './useBackButton';
export { default as useLinking } from './useLinking';
export { default as useScrollToTop } from './useScrollToTop';