Compare commits

..

3 Commits

Author SHA1 Message Date
Brent Vatne
6373b802dd Release 1.4.0 2018-03-03 09:59:22 -08:00
Brent Vatne
138151433d Add isFocused helper to navigation and fix withNavigationFocus (1.x branch) (#3651)
* Add isFocused helper to navigation and fix withNavigationFocus accordingly

* Fix snapshots

* Make flow pass on TabsWithNavigationFocus example
2018-03-03 09:58:19 -08:00
Brent Vatne
2744cb32b7 Cache child event subscriptions (1.x branch) (#3649)
* Cache child event subscriptions on StackNavigator on 1.x branch

* Cache child event subscribers on DrawerView and withCachedChildNavigation
2018-03-03 09:01:24 -08:00
9 changed files with 163 additions and 49 deletions

View File

@@ -114,10 +114,10 @@ const ExampleInfo = {
name: 'Animated Tabs Example',
description: 'Tab transitions have custom animations',
},
// TabsWithNavigationFocus: {
// name: 'withNavigationFocus',
// description: 'Receive the focus prop to know when a screen is focused',
// },
TabsWithNavigationFocus: {
name: 'withNavigationFocus',
description: 'Receive the focus prop to know when a screen is focused',
},
};
const ExampleRoutes = {
@@ -146,7 +146,7 @@ const ExampleRoutes = {
path: 'settings',
},
TabAnimations,
// TabsWithNavigationFocus: TabsWithNavigationFocus,
TabsWithNavigationFocus,
};
type State = {

View File

@@ -3,40 +3,75 @@
*/
import React from 'react';
import { SafeAreaView, Text } from 'react-native';
import { Button, SafeAreaView, Text } from 'react-native';
import { TabNavigator, withNavigationFocus } from 'react-navigation';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import SampleText from './SampleText';
const createTabScreen = (name, icon, focusedIcon, tintColor = '#673ab7') => {
const TabScreen = ({ isFocused }) => (
<SafeAreaView
forceInset={{ horizontal: 'always', top: 'always' }}
style={{
flex: 1,
alignItems: 'center',
justifyContent: 'center',
}}
>
<Text style={{ fontWeight: '700', fontSize: 16, marginBottom: 5 }}>
{'Tab ' + name.toLowerCase()}
class Child extends React.Component<any, any> {
render() {
return (
<Text style={{ color: this.props.isFocused ? 'green' : 'maroon' }}>
{this.props.isFocused
? 'I know that my parent is focused!'
: 'My parent is not focused! :O'}
</Text>
<Text>{'props.isFocused: ' + (isFocused ? ' true' : 'false')}</Text>
</SafeAreaView>
);
);
}
}
TabScreen.navigationOptions = {
tabBarLabel: name,
tabBarIcon: ({ tintColor, focused }) => (
<MaterialCommunityIcons
name={focused ? focusedIcon : icon}
size={26}
style={{ color: focused ? tintColor : '#ccc' }}
/>
),
};
const ChildWithNavigationFocus = withNavigationFocus(Child);
const createTabScreen = (name, icon, focusedIcon, tintColor = '#673ab7') => {
class TabScreen extends React.Component<any, any> {
static navigationOptions = {
tabBarLabel: name,
tabBarIcon: ({ tintColor, focused }) => (
<MaterialCommunityIcons
name={focused ? focusedIcon : icon}
size={26}
style={{ color: focused ? tintColor : '#ccc' }}
/>
),
};
state = { showChild: false };
render() {
const { isFocused } = this.props;
return (
<SafeAreaView
forceInset={{ horizontal: 'always', top: 'always' }}
style={{
flex: 1,
alignItems: 'center',
justifyContent: 'center',
}}
>
<Text style={{ fontWeight: '700', fontSize: 16, marginBottom: 5 }}>
{'Tab ' + name.toLowerCase()}
</Text>
<Text style={{ marginBottom: 20 }}>
{'props.isFocused: ' + (isFocused ? ' true' : 'false')}
</Text>
{this.state.showChild ? (
<ChildWithNavigationFocus />
) : (
<Button
title="Press me"
onPress={() => this.setState({ showChild: true })}
/>
)}
<Button
onPress={() => this.props.navigation.goBack(null)}
title="Back to other examples"
/>
</SafeAreaView>
);
}
}
return withNavigationFocus(TabScreen);
};

View File

@@ -1,6 +1,6 @@
{
"name": "react-navigation",
"version": "1.3.2",
"version": "1.4.0",
"description": "Routing and navigation for your React Native apps",
"main": "src/react-navigation.js",
"repository": {

View File

@@ -4,6 +4,7 @@
* Based on the 'action' events that get fired for this navigation state, this utility will fire
* focus and blur events for this child
*/
export default function getChildEventSubscriber(addListener, key) {
const actionSubscribers = new Set();
const willFocusSubscribers = new Set();

View File

@@ -82,6 +82,8 @@ class CardStack extends React.Component {
_screenDetails = {};
_childEventSubscribers = {};
componentWillReceiveProps(props) {
if (props.screenProps !== this.props.screenProps) {
this._screenDetails = {};
@@ -96,17 +98,39 @@ class CardStack extends React.Component {
});
}
componentDidUpdate() {
const activeKeys = this.props.transitionProps.navigation.state.routes.map(
route => route.key
);
Object.keys(this._childEventSubscribers).forEach(key => {
if (!activeKeys.includes(key)) {
delete this._childEventSubscribers[key];
}
});
}
_isRouteFocused = route => {
const { state } = this.props.navigation;
const focusedRoute = state.routes[state.index];
return route === focusedRoute;
};
_getScreenDetails = scene => {
const { screenProps, transitionProps: { navigation }, router } = this.props;
let screenDetails = this._screenDetails[scene.key];
if (!screenDetails || screenDetails.state !== scene.route) {
if (!this._childEventSubscribers[scene.route.key]) {
this._childEventSubscribers[scene.route.key] = getChildEventSubscriber(
navigation.addListener,
scene.route.key
);
}
const screenNavigation = addNavigationHelpers({
dispatch: navigation.dispatch,
state: scene.route,
addListener: getChildEventSubscriber(
navigation.addListener,
scene.route.key
),
isFocused: this._isRouteFocused.bind(this, scene.route),
addListener: this._childEventSubscribers[scene.route.key],
});
screenDetails = {
state: scene.route,

View File

@@ -17,6 +17,8 @@ export default class DrawerView extends React.PureComponent {
: this.props.drawerWidth,
};
_childEventSubscribers = {};
componentWillMount() {
this._updateScreenNavigation(this.props.navigation);
@@ -27,6 +29,17 @@ export default class DrawerView extends React.PureComponent {
Dimensions.removeEventListener('change', this._updateWidth);
}
componentDidUpdate() {
const activeKeys = this.props.navigation.state.routes.map(
route => route.key
);
Object.keys(this._childEventSubscribers).forEach(key => {
if (!activeKeys.includes(key)) {
delete this._childEventSubscribers[key];
}
});
}
componentWillReceiveProps(nextProps) {
if (
this.props.navigation.state.index !== nextProps.navigation.state.index
@@ -68,6 +81,12 @@ export default class DrawerView extends React.PureComponent {
}
};
_isRouteFocused = route => () => {
const { state } = this.props.navigation;
const focusedRoute = state.routes[state.index];
return route === focusedRoute;
};
_updateScreenNavigation = navigation => {
const { drawerCloseRoute } = this.props;
const navigationState = navigation.state.routes.find(
@@ -79,13 +98,18 @@ export default class DrawerView extends React.PureComponent {
) {
return;
}
if (!this._childEventSubscribers[navigationState.key]) {
this._childEventSubscribers[
navigationState.key
] = getChildEventSubscriber(navigation.addListener, navigationState.key);
}
this._screenNavigationProp = addNavigationHelpers({
dispatch: navigation.dispatch,
state: navigationState,
addListener: getChildEventSubscriber(
navigation.addListener,
navigationState.key
),
isFocused: this._isRouteFocused.bind(this, navigationState),
addListener: this._childEventSubscribers[navigationState.key],
});
};

View File

@@ -248,6 +248,7 @@ exports[`TabBarBottom renders successfully 1`] = `
"dispatch": undefined,
"getParam": [Function],
"goBack": [Function],
"isFocused": [Function],
"navigate": [Function],
"pop": [Function],
"popToTop": [Function],

View File

@@ -12,9 +12,13 @@ export default function withNavigationFocus(Component) {
navigation: propTypes.object.isRequired,
};
state = {
isFocused: false,
};
constructor(props, context) {
super();
this.state = {
isFocused: this.getNavigation(props, context).isFocused(),
};
}
componentDidMount() {
const navigation = this.getNavigation();
@@ -32,8 +36,8 @@ export default function withNavigationFocus(Component) {
this.subscriptions.forEach(sub => sub.remove());
}
getNavigation = () => {
const navigation = this.props.navigation || this.context.navigation;
getNavigation = (props = this.props, context = this.context) => {
const navigation = props.navigation || context.navigation;
invariant(
!!navigation,
'withNavigationFocus can only be used on a view hierarchy of a navigator. The wrapped component is unable to get access to navigation from props or context.'

View File

@@ -10,6 +10,8 @@ export default function withCachedChildNavigation(Comp) {
return class extends React.PureComponent {
static displayName = `withCachedChildNavigation(${displayName})`;
_childEventSubscribers = {};
componentWillMount() {
this._updateNavigationProps(this.props.navigation);
}
@@ -18,6 +20,23 @@ export default function withCachedChildNavigation(Comp) {
this._updateNavigationProps(nextProps.navigation);
}
componentDidUpdate() {
const activeKeys = this.props.navigation.state.routes.map(
route => route.key
);
Object.keys(this._childEventSubscribers).forEach(key => {
if (!activeKeys.includes(key)) {
delete this._childEventSubscribers[key];
}
});
}
_isRouteFocused = route => () => {
const { state } = this.props.navigation;
const focusedRoute = state.routes[state.index];
return route === focusedRoute;
};
_updateNavigationProps = navigation => {
// Update props for each child route
if (!this._childNavigationProps) {
@@ -28,13 +47,19 @@ export default function withCachedChildNavigation(Comp) {
if (childNavigation && childNavigation.state === route) {
return;
}
if (!this._childEventSubscribers[route.key]) {
this._childEventSubscribers[route.key] = getChildEventSubscriber(
navigation.addListener,
route.key
);
}
this._childNavigationProps[route.key] = addNavigationHelpers({
dispatch: navigation.dispatch,
state: route,
addListener: getChildEventSubscriber(
navigation.addListener,
route.key
),
isFocused: this._isRouteFocused.bind(this, route),
addListener: this._childEventSubscribers[route.key],
});
});
};