Skip to content

Instantly share code, notes, and snippets.

@lucas1richard
Last active April 24, 2018 16:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lucas1richard/e720015613ec1de4dfdb59168f39fa41 to your computer and use it in GitHub Desktop.
Save lucas1richard/e720015613ec1de4dfdb59168f39fa41 to your computer and use it in GitHub Desktop.
For react-navigation@1.0.0-beta.19

React Navigation 1.0.0-beta.19 Best Practices

Performance

Never pass the navigation prop to children

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.

Use the 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.

Use a snapshot of your home screen routes to make screens unmount

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);
};

Pragmatism

Store your params somewhere besides in the navigation prop

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.

Nested navigators have an empty <View /> heading

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.

Even the headers should not receive the navigation prop

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.

Create your own header components

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
  }
});

Tools

Create a switch router

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;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment