Skip to content

Instantly share code, notes, and snippets.

@RoryKelly
Last active January 24, 2022 05:58
Show Gist options
  • Select an option

  • Save RoryKelly/037c01916301798b4a5b8a40e0b3b87e to your computer and use it in GitHub Desktop.

Select an option

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

RoryKelly commented Nov 9, 2017 •

Copy link
Copy Markdown
Author

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

RoryKelly commented Nov 9, 2017 •

Copy link
Copy Markdown
Author

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