react-navigation - navigating from child component - react-native

I have a leaderboard which calls a component and passes it data to it like so:
_renderItem =({item}) => (
<childComponent
key={item._id}
id={item._id}
name={item.name}
/>
);
And inside the childComponent I try do this:
<TouchableOpacity onPress={() => this.props.navigation.navigate("Profile", { id: this.props.id})} >
<View>
<Right>
{arrowIcon}
</Right>
</View>
</TouchableOpacity>
Where I am hoping that it will then go to the profile page and grab the correct data based on the id passed to it. The issue is that when I click the arrow to go to the profile page I get the error Cannot read property 'navigate of undefined. I have put both the leaderboard and childComponent in my HomeDrawerrRoutes.js and MainStackRouter.js. Any help would be great, thanks.

There is an easy Solution for this,
use withNavigation . it's a higher order component which passes the navigation prop into a wrapped Component.
example child component
import React from 'react';
import { Button } from 'react-native';
import { withNavigation } from 'react-navigation';
class ChildComponent extends React.Component {
render() {
<View
onPress = {()=> this.props.navigation.navigate('NewComponent')}>
... logic
</View>
}
}
// withNavigation returns a component that wraps ChildComponent and passes in the
// navigation prop
export default withNavigation(ChildComponent);
for more details : https://reactnavigation.org/docs/en/connecting-navigation-prop.html

This is a 3 page example that shows how to pass the navigate function to a child component and how to customize props send to screens from within the StackNavigator
// subcomponent ... receives navigate from parent
const Child = (props) => {
return (
<TouchableOpacity
onPress={() => props.navigate(props.destination) }>
<Text>{props.text}>>></Text>
</TouchableOpacity>
);
}
// receives navigation from StackNavigator
const PageOne = (props) => {
return (
<View>
<Text>Page One</Text>
<Child
navigate={props.navigation.navigate}
destination="pagetwo" text="To page 2"/>
</View>
)
}
// receives custom props AND navigate inside StackNavigator
const PageTwo = (props) => (
<View>
<Text>{props.text}</Text>
<Child
navigate={props.navigation.navigate}
destination="pagethree" text="To page 3"/>
</View>
);
// receives ONLY custom props (no nav sent) inside StackNAvigator
const PageThree = (props) => <View><Text>{props.text}</Text></View>
export default App = StackNavigator({
pageone: {
screen: PageOne, navigationOptions: { title: "One" } },
pagetwo: {
screen: (navigation) => <PageTwo {...navigation} text="Page Deux" />,
navigationOptions: { title: "Two" }
},
pagethree: {
screen: () => <PageThree text="Page III" />,
navigationOptions: { title: "Three" }
},
});

The useNavigation hook was introduced in v5:
import * as React from 'react';
import { Button } from 'react-native';
import { useNavigation } from '#react-navigation/native';
export function ChildComponent() => {
const navigation = useNavigation();
return (
<Button
title="Back"
onPress={() => {
navigation.goBack();
}}
/>
);
}
Docs: https://reactnavigation.org/docs/use-navigation

For some reason if you don't want to use withNavigation, the following solution works too. You just have to pass navigation as a prop to your child component.
For example:
export default class ParentComponent extends React.Component {
render() {
return (
<View>
<ChildComponent navigation={this.props.navigation} />
</View>
);
}
}
And in child component:
const ChildComponent = (props) => {
return (
<View>
<TouchableOpacity
onPress={() => props.navigation.navigate('Wherever you want to navigate')}
/>
</View>
);
};
export default ChildComponent;

Related

How to access navigation outside main component?

I've been working with the default tabs project created with create-react-native-app.
So I created my own screen where this.props.navigation is accessible in the main (export default class) component. It works fine to do navigate('Search') from the button titled 'nav default'.
However, after many tries, I couldn't navigate from either the button in the headerRight or in my custom component MyComponent.
How do I change my alerts to instead do navigate('Search') like the main component?
import React from 'react';
import { Button, Dimensions, ScrollView, StyleSheet, Text, View } from 'react-native';
class MyComponent extends React.Component {
// i wish i could navigate from here
render() {
//const { navigate } = this.props.navigation; // this causes TypeError undefined is not an object (evaluating this.props.navigation.navigate)
return (
<View>
<Button title="nav from component" onPress={() => alert('This should navigate, not alert')} color="red" />
</View>
);
}
}
export default class MyScreen extends React.Component {
// i wish i could navigate from here too
static navigationOptions = {
title: 'Test',
headerRight: (
//<Button onPress={() =>navigate('Search')} /> // how do I achieve this?
<Button title="nav from header" onPress={() => alert('This should navigate, not alert')} />
),
};
render() {
const { navigate } = this.props.navigation;
return (
<ScrollView style={styles.container}>
<Button onPress={() =>navigate('Search')} title="nav default" />
<MyComponent />
</ScrollView>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: 15,
backgroundColor: '#fff',
},
});
There are two different problems. First, navigation is only passed as a prop to the components that are navigation screens. So, to access it from other components, such as your MyComponent, you have to pass it through props <MyComponent navigation={navigation} /> and then you can use this.props.navigation.navigate inside it. You can also use the withNavigation higher order component, but in that case the first approach is better.
The other problem is, if you want to access the navigation prop in navigationOptions, you should define it as a function, and not as an object. The implementation would be something like this:
static navigationOptions = ({ navigation }) => ({
title: 'Test',
headerRight: (
<Button onPress={() =>navigation.navigate('Search')} />
),
});

Pass Data between Pages in React native

Im new to react native and I'm stuck at following.
Im performing navigation (when clicked on alert view button) using the code below.
const {navigation} = this.props.navigation;
…
.
.
{ text: 'Done', onPress:() => {
navigate.push(HomeScreen);}
How can I pass data to another Page in React native? Can I declare the parameter global and just assign to it?
What would be the correct way of performing this and how would I go about it?
Note
This answer was written for react-navigation: "3.3.0". As there are newer versions available, which could bring changes, you should make sure that you check with the actual documentation.
Passing data between pages in react-navigation is fairly straight forward. It is clearly explained in the documentation here
For completeness let's create a small app that allows us to navigate from one screen to another passing values between the screens. We will just be passing strings in this example but it would be possible to pass numbers, objects and arrays.
App.js
import React, {Component} from 'react';
import AppContainer from './MainNavigation';
export default class App extends React.Component {
render() {
return (
<AppContainer />
)
}
}
MainNavigation.js
import Screen1 from './Screen1';
import Screen2 from './Screen2';
import { createStackNavigator, createAppContainer } from 'react-navigation';
const screens = {
Screen1: {
screen: Screen1
},
Screen2: {
screen: Screen2
}
}
const config = {
headerMode: 'none',
initialRouteName: 'Screen1'
}
const MainNavigator = createStackNavigator(screens,config);
export default createAppContainer(MainNavigator);
Screen1.js and Screen2.js
import React, {Component} from 'react';
import { View, StyleSheet, Text, Button } from 'react-native';
export default class Screen extends React.Component {
render() {
return (
<View style={styles.container}>
</View>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'white'
}
});
Here we have 4 files. The App.js which we will import the MainNavigation.js. The MainNavigation.js sets up a StackNavigator with two screens, Screen1.js and Screen2.js. Screen1 has been set as the initial screen for our StackNavigator.
Navigating between screens
We can navigate from Screen1 to Screen2 by using
this.props.navigation.navigate('Screen2');
and we can go back to Screen1 from Screen2 by using
this.props.navigation.goBack();
So code in Screen1 becomes
export default class Screen extends React.Component {
render() {
return (
<View style={styles.container}>
<Button title={'Go to screen 2'} onPress={() => this.props.navigation.navigate('Screen2')} />
</View>
)
}
}
And code in Screen2 becomes:
export default class Screen extends React.Component {
render() {
return (
<View style={styles.container}>
<Button title={'Go back'} onPress={() => this.props.navigation.goBack()} />
</View>
)
}
}
Now we can navigate between Screen1 and Screen2
Sending values from Screen1 to Screen2
To send a value between Screen1 and Screen2, two steps are involved. First we have to send it, secondly we have to capture it.
We can send a value by passing it as a second parameter. Notice how the text value is contained in an object.
this.props.navigation.navigate('Screen2', {text: 'Hello from Screen 1' });
And we can capture it in Screen2 by doing the following, the first value in getParams is the key the second value is the default value.
const text = this.props.navigation.getParams('text','nothing sent');
So Screen1 now becomes
export default class Screen extends React.Component {
render() {
return (
<View style={styles.container}>
<Button
title={'Go to screen 2'}
onPress={() => this.props.navigation.navigate('Screen2', {
text: 'Hello from screen 1'
})} />
</View>
)
}
}
And code in Screen2 becomes:
export default class Screen extends React.Component {
render() {
const text = this.props.navigation.getParam('text', 'nothing sent')
return (
<View style={styles.container}>
<Text>{text}</Text>
<Button
title={'Go back'}
onPress={() => this.props.navigation.goBack()} />
</View>
)
}
}
Sending values from Screen2 back to Screen1
The easiest way I have discovered to send a value from Screen2 to Screen1 is to pass a function to Screen2 from Screen1 that will update the state in Screen1 with the value that you want to send
So we can update Screen1 to look like this. First we set an initial value in state. Then we create a function that will update the state. Then we pass that function as a parameter. We will display the captured value from Screen2 in a Text component.
export default class Screen1 extends React.Component {
state = {
value: ''
}
receivedValue = (value) => {
this.setState({value})
}
render() {
return (
<View style={styles.container}>
<Button
title={'Go to screen 2'}
onPress={() => this.props.navigation.navigate('Screen2', {
text: 'Hello from Screen 1',
receivedValue: this.receivedValue }
)} />
<Text>{this.state.value}</Text>
</View>
)
}
}
Notice that we are passing the function receivedValue in the same way that we passed the text earlier.
Now we have to capture the value in Screen2 and we do that in a very similar way that we did previously. We use getParam to get the value, remembering to set our default. Then when we press our Go back button we update it to call the receivedValue function first, passing in the text that we want to send back.
export default class Screen2 extends React.Component {
render () {
const text = this.props.navigation.getParam('text', 'nothing sent');
const receivedValue = this.props.navigation.getParam('receivedValue', () => {});
return (
<View style={styles.container}>
<Button
title={'Go back'}
onPress={() => {
receivedValue('Hello from screen 2')
this.props.navigation.goBack()
}} />
<Text>{text}</Text>
</View>
);
}
}
Alternatives to using getParam
It is possible to not use the getParam method and instead access the values directly. If we were to do that we would not have the option of setting a default value. However it can be done.
In Screen2 we could have done the following:
const text = this.props.navigation.state.params.text;
const receivedValue = this.props.navigation.state.params.receivedValue;
Capturing values in lifecycle events (Screen1 to Screen2)
react-navigation allows you to capture values using the lifecycle events. There are a couple of ways that we can do this. We could use NavigationEvents or we could use listeners set in the componentDidMount
Here is how to set it up using NavigationEvents
import React, {Component} from 'react';
import { View, StyleSheet, Text } from 'react-native';
import { NavigationEvents } from 'react-navigation'; // you must import this
export default class Screen2 extends React.Component {
state = {
text: 'nothing passed'
}
willFocusAction = (payload) => {
let params = payload.state.params;
if (params && params.value) {
this.setState({value: params.value});
}
}
render() {
return (
<View style={styles.container}>
<NavigationEvents
onWillFocus={this.willFocusAction}
/>
<Text>Screen 2</Text>
<Text>{this.state.text}</Text>
</View>
)
}
}
Here is how to do it using listeners in the componentDidMount
export default class Screen2 extends React.Component {
componentDidMount () {
// we add the listener here
this.willFocusSubscription = this.props.navigation.addListener('willFocus', this.willFocusAction);
}
componentWillUmount () {
// we remove the listener here
this.willFocusSubscription.remove()
}
state = {
text: 'nothing passed'
}
willFocusAction = (payload) => {
let params = payload.state.params;
if (params && params.value) {
this.setState({value: params.value});
}
}
render() {
return (
<View style={styles.container}>
<Text>Screen 2</Text>
<Text>{this.state.text}</Text>
</View>
)
}
}
Passing navigation via components
In the above examples we have passed values from screen to screen. Sometimes we have a component on the screen and we may want to navigate from that. As long as the component is used within a screen that is part of a navigator then we can do it.
If we start from our initial template and construct two buttons. One will be a functional component the other a React component.
MyButton.js
// this is a functional component
import React, {Component} from 'react';
import { View, StyleSheet, Text, TouchableOpacity } from 'react-native';
export const MyButton = ({navigation, value, title}) => {
return (
<TouchableOpacity onPress={() => navigation.navigate('Screen2', { value })}>
<View style={styles.buttonStyle}>
<Text>{title}</Text>
</View>
</TouchableOpacity>
)
}
const styles = StyleSheet.create({
buttonStyle: {
width: 200,
height: 60,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'red'
}
});
MyOtherButton.js
// this is a React component
import React, {Component} from 'react';
import { View, StyleSheet, Text, TouchableOpacity } from 'react-native';
export default class MyOtherButton extends React.Component {
render() {
const { navigation, value, title } = this.props;
return (
<TouchableOpacity onPress={() => navigation.navigate('Screen2', { value })}>
<View style={styles.buttonStyle}>
<Text>{title}</Text>
</View>
</TouchableOpacity>
)
}
}
const styles = StyleSheet.create({
buttonStyle: {
width: 200,
height: 60,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'yellow'
}
});
Regardless of the type of component, notice that navigation is a prop. We must pass navigation to the component otherwise it will not work.
Screen1.js
import React, {Component} from 'react';
import { View, StyleSheet, Text, Button } from 'react-native';
import { MyButton } from './MyButton';
import MyOtherButton from './MyOtherButton';
export default class Screen1 extends React.Component {
render() {
return (
<View style={styles.container}>
<Text>Screen 1</Text>
<MyButton
title={'Press my button'}
navigation={this.props.navigation}
value={'this is a string passed using MyButton'}
/>
<MyOtherButton
title={'Press my other button'}
navigation={this.props.navigation}
value={'this is a string passed using MyOtherButton'}
/>
</View>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'white'
}
});
Notice in Screen1.js as it is contained in a StackNavigator it will have access to this.props.navigation. We can pass that through to our component as a prop. As long as we use that in our component then we should be able to navigate by using the components own functionality.
<MyButton
title={'Press my button'}
navigation={this.props.navigation} // pass the navigation here
value={'this is a string passed using MyButton'}
/>
Snacks
Here is a snack for passing params.
Here is a snack for passing params and capturing in lifecycle events.
Here is a snack passing navigation to components
1) On Home Screen:-
Initialise:-
constructor(props) {
super(props);
this.navigate = this.props.navigation.navigate; }
Send:-
this.navigate("DetailScreen", {
name: "Detail Screen",
about:"This is Details Screen Page"
});
2) On Detail Screen:-
Initialise:-
constructor(props) {
super(props);
this.params = this.props.navigation.state.params;
}
Retrive data:-
console.log(this.params.name);
console.log(this.params.about);
const {navigate} = this.props.navigation;
…
.
.
{ text: 'Done', onPress:() => {
navigate('homeScreen',...params);}
You can get those params like
const {params} = this.props.navigation.state
HomeScreen.js
this.props.navigation.navigate('Screen2',{ user_name: 'aaa',room_id:'100' });
Screen2.js
const params = this.props.route.params;
user_name = params.user_name;
room_id = params.room_id
You can easily send and receive your params with react-navigation like below
Send params:
{
text: 'Done',
onPress: () => {
this.props.navigation.navigate(
HomeScreen,
{param1: 'value1', param2: 'value2'}
);
}
}
Get params in HomeScreen:
const { navigation } = this.props;
var param1 = navigation.getParam('param1', 'NO-VALUE');
var param2 = navigation.getParam('param2', 'NO-VALUE');
the 'NO-VALUE' is default value, if there is not desired param
I am assuming that you are using react-navigation. So, in react-navigation we can pass data in two pieces:
Pass params to a route by putting them in an object as a second parameter to the navigation.navigate function:
this.props.navigation.navigate('RouteName', { /* params go here */ })
Read the params in your screen component:
this.props.navigation.getParam(paramName, someDefaultValue)
Alert Button
<Button
title="Alert View"
onPress={() => {
this.props.navigation.navigate('alerts', {
itemId: 86,
otherParam: 'anything you want here',
});
}}
/>
Screen:
const itemId = navigation.getParam('itemId', 'NO-ID');
const otherParam = navigation.getParam('otherParam', 'some default value')
Screen 1:
<Button title="Go Next"
onPress={() => navigation.navigate('SecondPage', { paramKey: userName })} />
Screen 2:
const SecondPage = ({route}) => {
....
....
<Text style={styles.textStyle}>
Values passed from First page: {route.params.paramKey}
</Text>
....
....
}

react native Flatlist navigation

I m getting error
TypeError: Cannot read property 'navigation' of undefined. I don't understand how to pass navigation component into each child so when a user presses an item it can navigate to employeeEdit component using React Navigation. i am newbie sorry if this is obvious.
import React, { Component } from 'react';
import { FlatList } from 'react-native';
import { connect } from 'react-redux';
//import { R } from 'ramda';
import _ from 'lodash';
import { employeesFetch } from '../actions';
import { HeaderButton } from './common';
import ListEmployee from './ListEmployee';
class EmployeeList extends Component {
static navigationOptions = ({ navigation }) => ({
headerRight: (
<HeaderButton onPress={() => navigation.navigate('employeeCreate')}>
Add
</HeaderButton>
)
});
componentWillMount() {
this.props.employeesFetch();
}
keyExtractor(item) {
return item.uid;
}
renderItem({ item }) {
return <ListEmployee employee={item} navigation={this.props.navigation} />;
}
render() {
return (
<FlatList
data={this.props.employees}
renderItem={this.renderItem} // Only for test
keyExtractor={this.keyExtractor}
navigation={this.props.navigation}
/>
);
}
}
const mapStateToProps = (state) => {
const employees = _.map(state.employees, (val, uid) => ({ ...val, uid }));
return { employees };
};
export default connect(mapStateToProps, { employeesFetch })(EmployeeList);
Here's the code for ListEmployee
import React, { Component } from 'react';
import {
Text,
StyleSheet,
TouchableWithoutFeedback,
View
} from 'react-native';
import { CardSection } from './common';
class ListEmployee extends Component {
render() {
const { employee } = this.props;
const { navigate } = this.props.navigation;
const { textStyle } = styles;
const { name } = this.props.employee;
return (
<TouchableWithoutFeedback onPress={() => navigate('employeeEdit', { employee })}>
<View>
<CardSection>
<Text style={textStyle}>{name}</Text>
</CardSection>
</View>
</TouchableWithoutFeedback>
);
}
}
/**
second argument in connect does 2 things. 1. dispatches all actions creators
return action objects to the store to be used by reducers; 2. creates props
of action creators to be used by components
**/
export default ListEmployee;
const styles = StyleSheet.create({
textStyle: {
fontSize: 18,
paddingLeft: 15,
}
});
This is one ES6 common pitfall. Don't worry my friend, you only have to learn it once to avoid them all over again.
Long story short, when you declare a method inside React Component, make it arrow function
So, change from this.
renderItem({ item }) {
to this
renderItem = ({ item }) => {
That should solve your problem, for some inconvenient reason, you can only access "this" if you declare your method as an arrow function, but not with normal declaration.
In your case, since renderItem is not an arrow function, "this" is not referred to the react component, therefore "this.props" is likely to be undefined, that is why it gave you this error Cannot read property 'navigation' of undefined since
this.props.navigation = (undefined).navigation
Inside your renderItem method, you can manage what happens when the user presses one an item of your FlatList:
renderItem({ item }) {
<TouchableOpacity onPress={() => { this.props.navigator.push({id: 'employeeEdit'})}} >
<ListEmployee employee={item} navigation={this.props.navigation} />
</TouchableOpacity>
}
Hope it help you!
A navigation sample
here VendorList is the structure rendered
<FlatList
numColumns={6}
data={state.vendoreList}
keyExtractor={(data) => data.id}
renderItem={({ item }) =>
<TouchableOpacity onPress={() => props.navigation.navigate("Home1")} >
<VendorList item={item} />
</TouchableOpacity>
}
/>
in ListEmployee
const {navigation}= this.props.navigation;
this use
<TouchableWithoutFeedback onPress={() => navigation.navigate('employeeEdit', { employee })}>
just need to modification on those two lines, i make text bold what changes you need to do

StackNavigator through Component gives undefined error

I was trying to use StackNavigator for navigation and it works when I use it to go from one screen to the other as explained here. But when I try to have a subcomponent to navigate through itself, the navigation doesn't seem to work and I couldn't find any solution to it.
As given in the code below, I'm trying to use the Test Component in which there is a button that can be clicked to move from HomeScreen to ChatScreen.
I'm pretty sure the solution is something basic, but I really can't find it anywhere.
Here's my code:
import React from 'react';
import {
AppRegistry,
Text,
View,
Button
} from 'react-native';
import { StackNavigator } from 'react-navigation';
class HomeScreen extends React.Component {
static navigationOptions = {
title: 'Welcome',
};
render() {
const { navigate } = this.props.navigation;
let userName = 'Ketan';
return (
<View>
<Text>Hello, Chat App!</Text>
<Button
onPress={() => navigate('Chat', { user: userName })}
title={"Chat with " + userName}
/>
<Test />
</View>
);
}
}
class ChatScreen extends React.Component {
static navigationOptions = ({ navigation }) => ({
title: `Chat with ${navigation.state.params.user}`,
});
render() {
const { params } = this.props.navigation.state;
return (
<View>
<Text>Chat with {params.user}</Text>
</View>
);
}
}
class Test extends React.Component {
render() {
const { navigate } = this.props.navigation;
return (
<View>
<Button
onPress={() => navigate('Chat', { user: 'TestBot' })}
title={'This is a test'}
/>
</View>
)
}
}
const NavApp = StackNavigator({
Home: { screen: HomeScreen },
Chat: { screen: ChatScreen },
});
AppRegistry.registerComponent('NavApp', () => NavApp);
Here's the error I'm getting:
Here's the demo to test: https://snack.expo.io/HyaT8qYob
I hope my question is clear enough of what I mean.
Since your Test component does not belong to navigation stack it doesn't have the navigation prop. You can do couple of things.
Simple one is to pass the navigation to the child component like the example below.
return (
<View>
<Text>Hello, Chat App!</Text>
<Button
onPress={() => navigate('Chat', { user: userName })}
title={"Chat with " + userName}
/>
<Test navigation={this.props.navigation} />
</View>
);
The second option is, you can use withNavigation from react-navigation. You can find more details about it here
import { Button } 'react-native';
import { withNavigation } from 'react-navigation';
const MyComponent = ({ to, navigation }) => (
<Button title={`navigate to ${to}`} onPress={() => navigation.navigate(to)} />
);
const MyComponentWithNavigation = withNavigation(MyComponent)
withNavigation
withNavigation is a higher order component which passes the
navigation prop into a wrapped component. It's useful when you
cannot pass the navigation prop into the component directly, or
don't want to pass it in case of a deeply nested child.

React Native, render function not rendering, although the trace indicates that passes through it

Solution added at the end of this question, inspired by https://github.com/facebook/react-native/issues/6268 from #grabbou
The curtain rises and the first scene 'Scene1' appears. Scene1 is a presentational component wrapped with 'connect' from react-redux framework to bind the status and actions to their props.
Actions work perfectly well and renders the state, the counter, on the screen.
Cliking forward to the second scene 'Scene2', exactly the same as the first component, but the props (the same as Scene1) are passed through passProps in renderScene within the Naviagator.
Every thing is OK, the actions are dispatched correctly, you can see on the trace, render function is invoked for painting the counter, again you can see in the trace, but DOES NOT WORK!. The inner component logs that is in the Scene1! What's wrong?
This is the trace, after going directly to Scene2 and click twice on <+> to increment the state.
It's a bug-Native React?
I am using
"react-native": "0.19.0",
"react-redux": "4.1.2",
"redux": "3.1.7",
This is all the code, if you can help me.
There are no concession to stylize the presentation, so the result on the screen is very simple.
1. The simple code of index.ios.js
'use strict';
import React, {
AppRegistry,
} from 'react-native';
import App from './src/App'
AppRegistry.registerComponent('App', () => App);
And 2. this is the code of the App.js:
'use strict';
import React, {
Navigator,
Component,
View, ListView, ScrollView,
Text, TouchableOpacity
} from 'react-native';
import { Provider, connect } from "react-redux";
import { createStore, applyMiddleware, combineReducers, bindActionCreators } from "redux";
import thunkMiddleware from "redux-thunk";
import createLogger from "redux-logger";
2.1 The redux part
// REDUX BEGIN
//Actions
const INCREMENT = 'INCREMENT'
const DECREMENT = 'DECREMENT'
//Actions creators
const increment = () => ({ type: INCREMENT })
const decrement = () => ({ type: DECREMENT })
//Redux Initial State
const initialState = {
counter: 0
}
//Reducer
const reducer = (state = initialState, action = {}) => {
let delta = 1
switch (action.type) {
case DECREMENT: delta = -1;
case INCREMENT:
return Object.assign({}, state, { counter: state.counter+delta })
default:
return state
}
}
//Redux Middelware
const loggerMiddleware = createLogger();
const createStoreWithMiddleware = applyMiddleware(
thunkMiddleware,
loggerMiddleware
)(createStore);
//Wrapper to bind state and actions to props on Presentational Component
const connectComponent = (component) => connect(
(state) => ({
counter: state.counter
}),
(dispatch) => ({
increment: () => dispatch(increment()),
decrement: () => dispatch(decrement())
})
)(component)
// REDUX END
2.2 The App root, with the Provider and the Navigator
// APP
export default class App extends Component {
render () {
return (
<Provider store={createStoreWithMiddleware(reducer, initialState)}>
<Navigator style={{flex: 1}}
initialRoute={{
name: 'Scene1',
component: connectComponent(Scene1),
}}
renderScene={ (route, navigator) => {
const Component = route.component;
return (
<View style={{flex: 1, marginTop:40}}>
<Component navigator={navigator} route={route} {...route.passProps} />
</View>
);
}}
/>
</Provider>
)
}
}
2.3.
The inner Component in both scenes to render the counter.
Has some traces, to show that the shouldComponentUpdate is triggered and return True (you has to Update!) with the time traced to show that is invoqued just some milliseconds after an action is dispatched.
And other to show that the render function is reached, but doesn't not render with in the Scene2.
The trace show that this component always he thought that was the Scene1!!
class Counter extends Component{
constructor (props, context){
super(props, context);
}
shouldComponentUpdate(nextProps, nextState){
//Begin log
const repeat = (str, times) => (new Array(times + 1)).join(str);
const pad = (num, maxLength) => repeat(`0`, maxLength - num.toString().length) + num;
const formatTime = (time) => `# ${pad(time.getHours(), 2)}:${pad(time.getMinutes(), 2)}:${pad(time.getSeconds(), 2)}.${pad(time.getMilliseconds(), 3)}`;
console.log('shouldComponentUpdate '+this.props.route.name+ ': '+ (nextProps.counter !== this.props.counter) +' '+formatTime(new Date()));
//End log
return nextProps.counter !== this.props.counter;
}
render() {
console.log('onRender: '+this.props.counter);
return (
<View>
<Text style={{fontSize: 100}}>{this.props.counter}</Text>
<TouchableOpacity onPress={()=>{this.props.increment()}} ><Text style={{fontSize: 40}}>{'<'}+{'>'}</Text></TouchableOpacity>
<TouchableOpacity onPress={()=>{this.props.decrement()}} ><Text style={{fontSize: 40}}>{'<'}-{'>'}</Text></TouchableOpacity>
<Text>----</Text>
</View>
)
}
}
2.4.
The two scenes, are equals, just the button to forward or backward
class Scene1 extends Component {
render() {
return (
<View>
<Text style={{fontSize: 40}}>Scene1</Text>
<Counter {...this.props}/>
<TouchableOpacity onPress={()=>{
this.props.navigator.push({
name: 'Scene2',
component: Scene2,
passProps: {...this.props}
})
}}>
<Text style={{fontSize: 20}}>{'<'}Forward{'>'} to Scene2</Text>
</TouchableOpacity>
</View>
)
}
}
class Scene2 extends Component {
render() {
return (
<View>
<Text style={{fontSize: 40}}>Scene2</Text>
<Counter {...this.props}/>
<TouchableOpacity onPress={()=>{
this.props.navigator.pop()
}} >
<Text style={{fontSize: 20}}>{'<'}Back{'>'} to Scene1</Text>
</TouchableOpacity>
</View>
)
}
}
At the end some 'hard copy' to show the 'show'
The Scene2 showing the counter, and the two buttons to dispatch actions.
Clicking theses actions doesn't render the counter, but the actions are dispatched correctly.
After just going to Scene2 and two clicks on <+> to increment the counter.
The Counter component is his trace show the route.name, but it it show is on Scene1! What is wrong here?
Well, the play is over, the curtain has fallen.
It is a very dramatic scene. (Just the Scene2)
I wonder why it does not work.
Native React issue?
Thanks to all
The Solution
from https://github.com/facebook/react-native/issues/6268
#grabbou inspired the changes, he proposes wrap the all App as a Container and then pass Store and Actions as simple props to all Scenes.
To make these changes create a new component the RootComponent and render the App connected to the Redux Store and Actions like this.
export default class RootComponent extends Component {
render () {
const AppContainer = connectComponent(App); //<< App has to be container
return (
<Provider store={createStoreWithMiddleware(reducer, initialState)}>
<AppContainer/>
</Provider>
)
}
}
Then App change removing the Provider and just passing the Scene1 as dumb component, and renderScene pass {...this.props} insted of {...route.passProps}
class App extends Component {
render () {
return (
<Navigator style={{flex: 1}}
initialRoute={{
name: 'Scene1',
component: Scene1,
}}
renderScene={ (route, navigator) => {
const Component = route.component;
return (
<View style={{flex: 1, marginTop:40}}>
<Component navigator={navigator} route={route} {...this.props} />
</View>
);
}}
/>
)
}
}
The remove passProps from navigator.push in Scene1, because already are passed as default in renderScene
<TouchableOpacity onPress={()=>{
this.props.navigator.push({
name: 'Scene2',
component: Scene2,
//passProps: {...this.props}
})
}}>
And this is all folks!
Thanks
NOTE: This is merely a copy/paste of the author provided solution above.
from https://github.com/facebook/react-native/issues/6268
#grabbou inspired the changes, he proposes wrap the all App as a Container and then pass Store and Actions as simple props to all Scenes.
To make these changes create a new component the RootComponent and render the App connected to the Redux Store and Actions like this.
export default class RootComponent extends Component {
render () {
const AppContainer = connectComponent(App); //<< App has to be container
return (
<Provider store={createStoreWithMiddleware(reducer, initialState)}>
<AppContainer/>
</Provider>
)
}
}
Then App change removing the Provider and just passing the Scene1 as dumb component, and renderScene pass {...this.props} insted of {...route.passProps}
class App extends Component {
render () {
return (
<Navigator style={{flex: 1}}
initialRoute={{
name: 'Scene1',
component: Scene1,
}}
renderScene={ (route, navigator) => {
const Component = route.component;
return (
<View style={{flex: 1, marginTop:40}}>
<Component navigator={navigator} route={route} {...this.props} />
</View>
);
}}
/>
)
}
}
The remove passProps from navigator.push in Scene1, because already are passed as default in renderScene
<TouchableOpacity onPress={()=>{
this.props.navigator.push({
name: 'Scene2',
component: Scene2,
//passProps: {...this.props}
})
}}>
And this is all folks!
Thanks