Last active
January 24, 2022 05:58
-
-
Save RoryKelly/037c01916301798b4a5b8a40e0b3b87e to your computer and use it in GitHub Desktop.
App to demonstrate problems using react navigation with react redux
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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>); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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>); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// @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) | |
} |
App
Repro Steps
- Open the app navigate to "D".
- Press "Increment counter".
- 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
App
Repro Steps
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