react-navigation / redux-helpers

Redux middleware and utils for React Navigation
Other
296 stars 43 forks source link

Nested Navigators with redux render twice #11

Closed rumbogs closed 6 years ago

rumbogs commented 6 years ago

Using:

react-navigation: '1.3.2'
react-navigation-redux-helpers: '1.0.2'

On first load, the Home Screen is mounted twice:

App.js

import React, { Component } from 'react';
import { addNavigationHelpers } from 'react-navigation';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';

import RootTab from './navigation/RootTab';
import addListener from './store/middleware';

@connect(state => ({
  navigation: state.navigation,
}))
class App extends Component {
  static propTypes = {
    dispatch: PropTypes.func.isRequired,
    navigation: PropTypes.shape({}).isRequired,
  };
  render() {
    const { dispatch, navigation } = this.props;
    return (
      <RootTab
        navigation={addNavigationHelpers({
          dispatch,
          state: navigation,
          addListener,
        })}
      />
    );
  }
}

middleware.js

import { createReduxBoundAddListener, createReactNavigationReduxMiddleware } from 'react-navigation-redux-helpers';

export const middleware = createReactNavigationReduxMiddleware('root', state => state.navigation);

export default createReduxBoundAddListener('root');

RootTab.js

import { TabNavigator } from 'react-navigation';

import HomeStack from './HomeStack';

import routes from '../constants/routes';

export default TabNavigator(
  {
    [routes.HOME_TAB]: {
      screen: HomeStack,
    },
  },
  {
    initialRouteName: routes.HOME_TAB,
  }
);

HomeStack.js

import { StackNavigator } from 'react-navigation';

import { Home } from '../screens';
import routes from '../constants/routes';

export default StackNavigator(
  {
    [routes.HOME_STACK]: {
      screen: Home,
    },
  },
  {
    initialRouteName: routes.HOME_STACK,
  }
);

Home.js

import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { NavigationActions } from 'react-navigation';
import { Alert } from 'react-native';

import { showAlert } from '../../actions/ui';
import HomeView from './Home.view';
import { LogoHeaderTitle } from '../../components/';
import routes from '../../constants/routes';

@connect(
  ({ alert }) => ({ alert }),
  dispatch => ({
    showAlert: (question, buttons) => dispatch(showAlert(question, buttons)),
  })
)
class HomeScreen extends Component {
  static navigationOptions = () => ({
    headerTitle: <LogoHeaderTitle />,
  });
  static propTypes = {
    navigation: PropTypes.shape({
      dispatch: PropTypes.func,
    }).isRequired,
    showAlert: PropTypes.func.isRequired,
    alert: PropTypes.shape({
      question: PropTypes.string,
      buttons: PropTypes.arrayOf(
        PropTypes.shape({
          label: PropTypes.string,
          onClick: PropTypes.func,
        })
      ),
    }).isRequired,
  };

  componentDidMount() {
    console.log('did mount: HOME');
  }

  componentWillReceiveProps = nextProps => {
    const { question, buttons } = nextProps.alert;
    console.log(this.props);
    if (this.props.alert.question.length === 0 && nextProps.alert.question.length > 0) {
      // Alert.alert('', question, buttons);
    }
  };

  handleAlert = () => {
    const { showAlert, navigation } = this.props;
    showAlert('Question?', [
      {
        text: '1',
        onPress: () => navigation.dispatch(NavigationActions.navigate({ routeName: routes.TEST })),
      },
      {
        text: '2',
        onPress: () => console.log('navigate to 2'),
      },
      {
        text: 'Cancel',
        style: 'cancel',
      },
    ]);
  };

  render() {
    console.log('render');
    return <HomeView onAlert={this.handleAlert} />;
  }
}

export default HomeScreen;

NavReducer.js

import RootTabNavigator from '../navigation/RootTab';
import routes from '../constants/routes';

export const initialState = RootTabNavigator.router.getStateForAction(
  RootTabNavigator.router.getActionForPathAndParams(routes.HOME_TAB)
);

export default (state = initialState, action) => {
  const nextState = RootTabNavigator.router.getStateForAction(action, state);

  return nextState || state;
};

When starting the app, the debugger displays 2 console logs of render and did mount: HOME.

I'm expecting to only render once, as the default React Navigation behaviour, without Redux.

Edit: Seems that not calling setting state to initialState in the NavReducer fixes this, if that is the issue, please update documentation on Redux Integration on https://reactnavigation.org/docs/redux-integration.html

Ashoat commented 6 years ago

You can avoid this either by using React.PureComponent, or passing initialState to createStore directly instead of setting it in the reducer. The instructions will be updated soon, but as it stands we’ve integrated a function to create a default reducer that does initialize an undefined state by default. We do this to support the use case where people don’t specify their initialState to createStore.