Skip to content

Instantly share code, notes, and snippets.

@RoryKelly
Last active January 24, 2022 05:58
Show Gist options
  • Save RoryKelly/037c01916301798b4a5b8a40e0b3b87e to your computer and use it in GitHub Desktop.
Save RoryKelly/037c01916301798b4a5b8a40e0b3b87e to your computer and use it in GitHub Desktop.
App to demonstrate problems using react navigation with react redux
/**
* Sample React Native App
* https://github.com/facebook/react-native
* @flow
*/
import React from 'react';
import {combineReducers, createStore} from 'redux'
import {Button, Text, View} from "react-native";
import {addNavigationHelpers, NavigationActions, StackNavigator} from "react-navigation";
import {connect, Provider} from 'react-redux'
// React component that displays a title and a name.
class A extends React.Component {
componentWillMount() {
console.log(this.props.name + " mounted")
}
componentWillUnmount() {
console.log(this.props.name + " unmounted")
}
render() {
console.log(this.props.name + " Rendered");
return (
<View style={{flexGrow: 1}}>
<Button
onPress={this.props.onClickNavigate}
title={"Nativate to " + this.props.name}
/>
<Text style={{
fontSize: 20,
fontWeight: 'bold',
}}>
{"Counter : " + this.props.counter}
</Text>
<Button
style={{marginTop: 12}}
onPress={this.props.onIncrement}
title="Dispatch"
/>
</View>
);
}
}
// react-redux connect functions
const getState = (name) => (state) => ({counter: state.counter, name});
const mapDispatchToProps = (dispatch) => ({dispatch: dispatch});
const mergeProps = (nextScreen) => (stateProps, dispatchProps) => {
const dispatch = dispatchProps.dispatch;
return ({
...stateProps,
onClickNavigate: () => dispatch(NavigationActions.navigate({routeName: nextScreen})),
onIncrement: () => dispatch({type: 'INCREMENT'}),
});
};
// stack navigator where each item is connected to a redux store via connect
export const ModalStack = StackNavigator({
A: {
screen: connect(getState('A'), mapDispatchToProps, mergeProps('B'))(A),
navigationOptions: {title: "A"}
},
B: {
screen: connect(getState('B'), mapDispatchToProps, mergeProps('C'))(A),
navigationOptions: {title: "B"}
},
C: {
screen: connect(getState('C'), mapDispatchToProps, mergeProps('D'))(A),
navigationOptions: {title: "C"}
},
D: {
screen: connect(getState('D'), mapDispatchToProps, mergeProps('A'))(A),
navigationOptions: {title: "D"}
},
});
// stack nav reducer see https://reactnavigation.org/docs/guides/redux
const initialState = ModalStack.router.getStateForAction(ModalStack.router.getActionForPathAndParams('A'));
const navReducer = (state = initialState, action) => {
const nextState = ModalStack.router.getStateForAction(action, state);
return nextState || state;
};
// reducer that manages a counter
function counter(state = 0, action) {
console.log("got action " + action.type);
switch (action.type) {
case 'INCREMENT':
return state + 1;
default:
return state
}
}
const appReducer = combineReducers({
nav: navReducer,
counter: counter
});
let store = createStore(appReducer);
// redux integrated stack navigator https://reactnavigation.org/docs/guides/redux
class App extends React.Component {
render() {
return (
<ModalStack navigation={addNavigationHelpers({
dispatch: this.props.dispatch,
state: this.props.nav,
})}/>
);
}
}
const AppWithNavigationState = connect((state) => ({nav: state.nav}))(App);
export const rootComponent = () =>
(<Provider store={store}>
<AppWithNavigationState/>
</Provider>);
/**
* Sample React Native App
* https://github.com/facebook/react-native
* @flow
*/
import React from 'react';
import {combineReducers, createStore} from 'redux'
import {Button, Text, View} from "react-native";
import {addNavigationHelpers, NavigationActions, StackNavigator} from "react-navigation";
import {connect, Provider} from 'react-redux'
import {unmountWhenInvisible} from "./unmounter";
// React component that displays a title and a name.
class A extends React.Component {
componentWillMount() {
console.log(this.props.name + " mounted")
}
componentWillUnmount() {
console.log(this.props.name + " unmounted")
}
render() {
console.log(this.props.name + " Rendered");
return (
<View style={{flexGrow: 1}}>
<Button
onPress={this.props.onClickNavigate}
title={"Nativate to " + this.props.name}
/>
<Text style={{
fontSize: 20,
fontWeight: 'bold',
}}>
{"Counter : " + this.props.counter}
</Text>
<Button
style={{marginTop: 12}}
onPress={this.props.onIncrement}
title="Increment Counter"
/>
</View>
);
}
}
// react-redux connect functions
const getState = (name) => (state) => ({counter: state.counter, name});
const mapDispatchToProps = (dispatch) => ({dispatch: dispatch});
const mergeProps = (nextScreen) => (stateProps, dispatchProps) => {
const dispatch = dispatchProps.dispatch;
return ({
...stateProps,
onClickNavigate: () => dispatch(NavigationActions.navigate({routeName: nextScreen})),
onIncrement: () => dispatch({type: 'INCREMENT'}),
});
};
// stack navigator where each item is connected to a redux store via connect
export const ModalStack = StackNavigator({
A: {
screen: unmountWhenInvisible(connect(getState('A'), mapDispatchToProps, mergeProps('B'))(A), "A"),
navigationOptions: {title: "A"}
},
B: {
screen: unmountWhenInvisible(connect(getState('B'), mapDispatchToProps, mergeProps('C'))(A), "B"),
navigationOptions: {title: "B"}
},
C: {
screen: unmountWhenInvisible(connect(getState('C'), mapDispatchToProps, mergeProps('D'))(A), "C"),
navigationOptions: {title: "C"}
},
D: {
screen: unmountWhenInvisible(connect(getState('D'), mapDispatchToProps, mergeProps('A'))(A), "D"),
navigationOptions: {title: "D"}
},
});
// stack nav reducer see https://reactnavigation.org/docs/guides/redux
const initialState = ModalStack.router.getStateForAction(ModalStack.router.getActionForPathAndParams('A'));
const navReducer = (state = initialState, action) => {
const nextState = ModalStack.router.getStateForAction(action, state);
return nextState || state;
};
// reducer that manages a counter
function counter(state = 0, action) {
console.log("got action " + action.type);
switch (action.type) {
case 'INCREMENT':
return state + 1;
default:
return state
}
}
const appReducer = combineReducers({
nav: navReducer,
counter: counter
});
let store = createStore(appReducer);
// redux integrated stack navigator https://reactnavigation.org/docs/guides/redux
class App extends React.Component {
render() {
return (
<ModalStack navigation={addNavigationHelpers({
dispatch: this.props.dispatch,
state: this.props.nav,
})}/>
);
}
}
const AppWithNavigationState = connect((state) => ({nav: state.nav}))(App);
export const rootComponent = () =>
(<Provider store={store}>
<AppWithNavigationState/>
</Provider>);
// @flow
import * as React from "react";
import {connect} from "react-redux";
function getCurrentRouteName(navigationState) {
if (!navigationState) {
return null;
}
const route = navigationState.routes[navigationState.index];
// dive into nested navigators
if (route.routes) {
return getCurrentRouteName(route);
}
return route.routeName;
}
export function unmountWhenInvisible(WrappedComponent, screenName) {
const mapStateToFeedProps = (state) => ({
screenVisible: getCurrentRouteName(state.nav) === screenName,
});
const wrapper = (props) => {
if (props.screenVisible) {
return <WrappedComponent/>
}
return null;
};
return connect(mapStateToFeedProps)(wrapper)
}
@RoryKelly
Copy link
Author

RoryKelly commented Nov 9, 2017

App

Repro Steps

  1. Open the app navigate to "D".
  2. Press "Increment counter"
  3. Notice that every screen is rerendered

Below I have attached an annotated log:

Navigating to D

A mounted
A Rendered
got action Navigation/NAVIGATE
B mounted
B Rendered
got action Navigation/NAVIGATE
C mounted
C Rendered
got action Navigation/NAVIGATE
D mounted
D Rendered

Press "Increment counter" notice that all screens rerender

got action INCREMENT
A Rendered
B Rendered
C Rendered
D Rendered

@RoryKelly
Copy link
Author

RoryKelly commented Nov 9, 2017

App

Repro Steps

  1. Open the app navigate to "D".
  2. Press "Increment counter".
  3. Notice only the current screen is rerendered.

Below I have attached an annotated log:

Navigating to D

got action Navigation/NAVIGATE
B mounted
B Rendered
A unmounted
got action Navigation/NAVIGATE
C mounted
C Rendered
B unmounted
got action Navigation/NAVIGATE
D mounted
D Rendered
C unmounted

Press "Increment counter"

got action INCREMENT
D Rendered

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment