The navigation prop is updated with every screen change, and since every screen is mounted, they will all be re-rendered unless you provide a shouldComponentUpdate
lifecycle method.
Even if you don't think you'll need it, you should use it. Don't let your components updated unless the props you care about change. Combine this with the reselect
library for double control.
If you navigate to your home screen from a different screen, it may get mounted again and the different screen will also stay mounted. Instead, make a snapshot of your routes the first time you go to your home screen and force that state when you navigate to it.
Example
let dashboardState = null;
AppNavigator.router.getStateForAction = (action, state) => {
// Explanation for the line below is in `Tools/Create a switch router` of this document
if (action.type === SWITCH && action.routeName && state) { // This will probably be different for you
const nextState = prevGetStateForAction({ ...action, type: NavigationActions.NAVIGATE }, state);
const nextNavState = {
...nextState,
routes: newRoutes,
index: newIndex,
params: action.params
};
// Make the snapshot if it doesn't exist
if (!dashboardState) {
dashboardState = nextNavState;
}
// This is where we force the state
return action.routeName === '/dashboard/overview' ? dashboardState : nextNavState;
}
return prevGetStateForAction(action, state);
};
I recommend using redux. Have a navigation params reducer which listens for navigation actions and holds any params provided. Any screens that need access to those params can access them from the redux state instead of the navigation
prop.
Each individual screen should have it's own header. If the top level navigator also has a header, you'll end up with two on the screen. Setting the top level navigator header to null
or headerMode to none
will hide the child routes' headers as well.
The headers will also re-render if they have the navigation prop and any of the routes or state changes. It's far better to connect them to dispatch and use NavigationActions
provided by the react-navigation library.
Screens in react native have headers, and the api for designing headers is not sufficient. It's better to build your own headers.
Example
// src/components/NavHeadings/Stack.js
import React from 'react';
import { View, StyleSheet, Text } from 'react-native';
import { NavigationActions } from 'react-navigation';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import Touchable from 'components/Touchable';
import { MaterialIcons, Feather } from '@expo/vector-icons';
import { nav } from 'containers/Navigation/actions';
class Stack extends React.Component {
shouldComponentUpate() {
return false;
}
render() {
const { title, navTo, goBack, closeRoute, noCloseOption, onClose } = this.props;
return (
<View style={styles.wrapper}>
<Touchable onPress={goBack}>
<Feather size={30} name="chevron-left" />
</Touchable>
<Text bold style={{ fontSize: 20 }}>{title}</Text>
{!noCloseOption ? (
<Touchable onPress={onClose || (() => navTo(closeRoute || '/dashboard/overview'))}>
<MaterialIcons size={30} name="close" style={{ textAlign: 'center' }} />
</Touchable>
) : (
<View style={{ width: 30 }} />
)}
</View>
);
}
}
Stack.propTypes = {
title: PropTypes.string,
closeRoute: PropTypes.string,
navTo: PropTypes.func,
onClose: PropTypes.func,
noCloseOption: PropTypes.bool,
goBack: PropTypes.func
};
const mapDispatchToProps = (dispatch) => ({
goBack: () => dispatch(NavigationActions.goBack()),
navTo: (routeName) => dispatch(nav(routeName))
});
export default connect(null, mapDispatchToProps)(Stack);
const styles = StyleSheet.create({
wrapper: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
backgroundColor: '#fff',
height: 60,
marginTop: -10,
paddingHorizontal: 12,
paddingVertical: 18,
borderBottomWidth: 1
}
});
This is beneficial for authorization flows because it prevents the user from accidentally swiping back to a different route on the navigation. It can also be used to make sure only one screen on a stack is mounted. The performance could be dramatically improved with this if the other best practices are not followed.
Example
// src/containers/Navigation/reducer.js
import { NavigationActions } from 'react-navigation';
import { AppNavigator } from './AppNavigation';
import { SWITCH } from './constants';
/**
* The AppNavigator is a StackNavigator
*/
/** Save the default `getStateForAction` function */
const prevGetStateForAction = AppNavigator.router.getStateForAction;
AppNavigator.router.getStateForAction = (action, state) => {
/** If the action type is `SWITCH` and there is an action `routeName` */
if (action.type === SWITCH && action.routeName && state) {
/**
* Get the next state first
* --
* You must give the StackRouter a type of `Navigation/NAVIGATE` even though you're listening
* for SWITCH
*/
const nextState = prevGetStateForAction({ ...action, type: NavigationActions.NAVIGATE }, state);
/** Filter for the route that has the routeName provided */
const newRoutes = nextState.routes.filter((route) => route.routeName === action.routeName);
/**
* If it wasn't found, let you (your team) know they provided the wrong route,
* and return the next state
*/
if (!newRoutes.length) {
console.warn(`No routeName ${action.routeName} found on this stack`);
return nextState;
}
/** Return your customized state */
return {
...nextState,
routes: newRoutes,
index: 0,
params: action.params
};
}
return prevGetStateForAction(action, state);
};
/** Use it in your redux store */
const initialState = AppNavigator.router.getStateForAction(NavigationActions.init());
const { getStateForAction } = AppNavigator.router;
function navReducer(state = initialState, action) {
let nextState;
switch (action.type) {
default:
nextState = getStateForAction(action, state);
break;
}
return nextState || state;
}
export default navReducer;