Flux (alt), TabBarIOS, and Listeners and tabs that have not yet been touched / loaded - react-native

I've got a problem that I'm sure has a simple solution, but I'm new to React and React Native so I'm not sure what I'm missing.
My app has a TabBarIOS component at its root, with two tabs: TabA and TabB. TabB is subscribed to events from a Flux store (I'm using alt) that TabA creates. TabA basically enqueues items that TabB plays. This part of the code is fine and works as expected.
The problem is that TabA is the default tab so the user can use TabA an enqueue items, but because TabB hasn't been touched/clicked the TabB component hasn't been created so it's listener hasn't been registered. Only when TabB is pressed does it get created and correctly receive events.
So how can I ensure the TabB component gets created when the TabBarIOS component is rendered? Do I need to something hacky like set the active tab to TabB on initial load and flip it back to TabA before the user does anything?

Yes, you'll need to do something hacky if you're not using a Navigator component. If you're using Navigatoryou can specify a set of routes to initially mount with the initialRouteStackprop. This is however going to need you to modify a bit the way your app works I think.
If not using Navigator, you'll indeed have to do something hacky as you suggested. I've set up a working example here based on RN's TabBar example.
Below you'll find the code of this example, check the console.log (they don't seem to work on rnplay) to see that that components are mounted on opening the app.
Example Code
var React = require('react-native');
var {
AppRegistry,
Component,
Image,
StyleSheet,
TabBarIOS,
Text,
View
} = React;
import _ from 'lodash';
var base64Icon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEsAAABLCAQAAACSR7JhAAADtUlEQVR4Ac3YA2Bj6QLH0XPT1Fzbtm29tW3btm3bfLZtv7e2ObZnms7d8Uw098tuetPzrxv8wiISrtVudrG2JXQZ4VOv+qUfmqCGGl1mqLhoA52oZlb0mrjsnhKpgeUNEs91Z0pd1kvihA3ULGVHiQO2narKSHKkEMulm9VgUyE60s1aWoMQUbpZOWE+kaqs4eLEjdIlZTcFZB0ndc1+lhB1lZrIuk5P2aib1NBpZaL+JaOGIt0ls47SKzLC7CqrlGF6RZ09HGoNy1lYl2aRSWL5GuzqWU1KafRdoRp0iOQEiDzgZPnG6DbldcomadViflnl/cL93tOoVbsOLVM2jylvdWjXolWX1hmfZbGR/wjypDjFLSZIRov09BgYmtUqPQPlQrPapecLgTIy0jMgPKtTeob2zWtrGH3xvjUkPCtNg/tm1rjwrMa+mdUkPd3hWbH0jArPGiU9ufCsNNWFZ40wpwn+62/66R2RUtoso1OB34tnLOcy7YB1fUdc9e0q3yru8PGM773vXsuZ5YIZX+5xmHwHGVvlrGPN6ZSiP1smOsMMde40wKv2VmwPPVXNut4sVpUreZiLBHi0qln/VQeI/LTMYXpsJtFiclUN+5HVZazim+Ky+7sAvxWnvjXrJFneVtLWLyPJu9K3cXLWeOlbMTlrIelbMDlrLenrjEQOtIF+fuI9xRp9ZBFp6+b6WT8RrxEpdK64BuvHgDk+vUy+b5hYk6zfyfs051gRoNO1usU12WWRWL73/MMEy9pMi9qIrR4ZpV16Rrvduxazmy1FSvuFXRkqTnE7m2kdb5U8xGjLw/spRr1uTov4uOgQE+0N/DvFrG/Jt7i/FzwxbA9kDanhf2w+t4V97G8lrT7wc08aA2QNUkuTfW/KimT01wdlfK4yEw030VfT0RtZbzjeMprNq8m8tnSTASrTLti64oBNdpmMQm0eEwvfPwRbUBywG5TzjPCsdwk3IeAXjQblLCoXnDVeoAz6SfJNk5TTzytCNZk/POtTSV40NwOFWzw86wNJRpubpXsn60NJFlHeqlYRbslqZm2jnEZ3qcSKgm0kTli3zZVS7y/iivZTweYXJ26Y+RTbV1zh3hYkgyFGSTKPfRVbRqWWVReaxYeSLarYv1Qqsmh1s95S7G+eEWK0f3jYKTbV6bOwepjfhtafsvUsqrQvrGC8YhmnO9cSCk3yuY984F1vesdHYhWJ5FvASlacshUsajFt2mUM9pqzvKGcyNJW0arTKN1GGGzQlH0tXwLDgQTurS8eIQAAAABJRU5ErkJggg==';
class StackOverflowApp extends Component {
constructor(props) {
super(props);
this.state = {
selectedTab: 'blueTab',
notifCount: 0,
presses: 0
};
}
_renderContent = (color, pageText, num) => {
return (
<View style={[styles.tabContent, {backgroundColor: color}]}>
<Text style={styles.tabText}>{pageText}</Text>
<Text style={styles.tabText}>{num} re-renders of the {pageText}</Text>
</View>
);
};
componentWillMount() {
this.setState({selectedTab: 'redTab'});
}
componentDidMount() {
this.setState({selectedTab: 'blueTab'});
}
render () {
return (
<View style={{flex: 1}}>
<TabBarIOS
tintColor="white"
barTintColor="darkslateblue">
<TabBarIOS.Item
title="Blue Tab"
icon={{uri: base64Icon, scale: 3}}
selected={this.state.selectedTab === 'blueTab'}
onPress={() => {
this.setState({
selectedTab: 'blueTab',
});
}}>
<Page1 />
</TabBarIOS.Item>
<TabBarIOS.Item
systemIcon="history"
badge={this.state.notifCount > 0 ? this.state.notifCount : undefined}
selected={this.state.selectedTab === 'redTab'}
onPress={() => {
this.setState({
selectedTab: 'redTab'
});
}}>
<Page2 />
</TabBarIOS.Item>
</TabBarIOS>
</View>
);
};
}
class Page1 extends Component {
static route() {
return {
component: Page1
}
};
constructor(props) {
super(props);
}
componentWillMount() {
console.log('page 1 mount');
}
componentWillUnmount() {
console.log('page 1 unmount');
}
render() {
return (
<View style={styles.tabContent}>
<Text style={styles.tabText}>Page 1</Text>
</View>
);
}
}
class Page2 extends Component {
static route() {
return {
component: Page2
}
};
constructor(props) {
super(props);
}
componentWillMount() {
console.log('page 2 mount');
}
componentWillUnmount() {
console.log('page 2 unmount');
}
render() {
return (
<View style={styles.tabContent}>
<Text style={styles.tabText}>Page 2</Text>
</View>
);
}
}
const styles = StyleSheet.create({
tabContent: {
flex: 1,
backgroundColor: 'green',
alignItems: 'center',
},
tabText: {
color: 'white',
margin: 50,
},
});
AppRegistry.registerComponent('StackOverflowApp', () => StackOverflowApp);

Related

How do I navigate to a sub-screen after the first componentDidMount?

If I have a screen that receives route params, does some processing, and then re-routes to a sub-screen, this works if the screen was previously mounted but I get the following error if I try this after the first componentDidMount:
The action 'NAVIGATE' with payload {"name":"Chat","params":{"name":"Person2"}} was not handled by any navigator.
Do you have a screen named 'Chat'?
If you're trying to navigate to a screen in a nested navigator, see https://reactnavigation.org/docs/nesting-navigators#navigating-to-a-screen-in-a-nested-navigator.
This is a development-only warning and won't be shown in production.
[...]
Here are the highlights:
Tab navigator (App) with home (HomeScreen) and chats (ChatsScreenStack) tabs.
The chats tab is a stack navigator with a chats list to list all chats (ChatsListScreen) and a chat screen to show a particular chat (ChatScreen).
The chats tab stack navigator (ChatsScreenStack) has a componentDidUpdate which checks if the name prop has been updated, and, if so, it navigates to the chat tab.
The chats tab stack navigator also has a constructor which checks if it was created with a name prop, and, if so, it saves it off to a field and does the same navigation as above in componentDidMount.
Item 3 works but Item 4 doesn't work. Is this because react-navigation hasn't built up its navigation state at the time of the first componentDidMount? If so, how do I get a callback when react-navigation is ready?
Below is a reproduction (Snack link, Github link). If you launch, and click on ChatsTab, click back on HomeTab, and then click on the button it works. However, if you launch, and immediately click on the HomeTab button, it gives the error (in development mode; on the snack, it will navigate to the chats list rather than the chat screen).
import * as React from 'react';
import { Button, FlatList, Text, View } from 'react-native';
import { NavigationContainer } from '#react-navigation/native';
import { createBottomTabNavigator } from '#react-navigation/bottom-tabs';
import { createStackNavigator } from '#react-navigation/stack';
class ChatScreen extends React.Component {
render() {
return (
<View style={{ padding: 10 }}>
<Text>Chat with {this.props.route.params.name}</Text>
</View>
);
}
}
class ChatsListScreen extends React.Component {
render() {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<FlatList
data={[ {name: "Person1", key: "1"}, {name: "Person2", key: "2"}]}
renderItem={(data) => {
return (
<View key={data.item.key} style={{ margin: 10 }}>
<Button
title={data.item.name}
onPress={() => this.props.navigation.navigate("Chat", { name: data.item.name })}
/>
</View>
);
}}
/>
</View>
);
}
}
const ChatsStack = createStackNavigator();
class ChatsScreenStack extends React.Component {
constructor(props) {
super(props);
if (props.route && props.route.params && props.route.params.name) {
this.pendingReroute = props.route.params.name;
}
}
componentDidMount() {
if (this.pendingReroute) {
this.props.navigation.navigate("Chat", { name: this.pendingReroute });
}
}
componentDidUpdate(prevProps) {
let updated = false;
if (this.props.route && this.props.route.params.name) {
updated = true;
if (prevProps.route && prevProps.route.params && prevProps.route.params.name == this.props.route.params.name) {
updated = false;
}
}
if (updated) {
this.props.navigation.navigate("Chat", { name: this.props.route.params.name });
}
}
render() {
return (
<ChatsStack.Navigator>
<ChatsStack.Screen name="Chats" component={ChatsListScreen} />
<ChatsStack.Screen name="Chat" component={ChatScreen} />
</ChatsStack.Navigator>
);
}
}
class HomeScreen extends React.Component {
render() {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Button
title="Navigate to Person2"
onPress={() => this.props.navigation.navigate("ChatsTab", { name: "Person2" })}
/>
</View>
);
}
}
const Tabs = createBottomTabNavigator();
export default class App extends React.Component {
render() {
return (
<NavigationContainer>
<Tabs.Navigator>
<Tabs.Screen name="HomeTab" component={HomeScreen} />
<Tabs.Screen name="ChatsTab" component={ChatsScreenStack} />
</Tabs.Navigator>
</NavigationContainer>
);
}
}
Yes, this was related to react-navigation not being ready in componentDidMount. I needed to handle the focus event:
class ChatsScreenStack extends React.Component {
constructor(props) {
super(props);
if (props.route && props.route.params && props.route.params.name) {
this.pendingReroute = props.route.params.name;
}
}
componentDidMount() {
this.props.navigation.addListener(
"focus",
this.onFocus
);
}
onFocus = () => {
if (this.pendingReroute) {
const name = this.pendingReroute;
this.pendingReroute = null;
this.props.navigation.navigate("Chat", { name: this.pendingReroute });
}
}
[...]

setAccessibilityFocus using ref not working

I'm using the ref prop along with findNodeHandle on a bunch of components in order to be able to trigger AccessibilityInfo.setAccessibilityFocus. However, it's not always working as expected. Sometimes the reference is null even though componentDidMount has executed.
I'm often using setAccessibilityFocus in order to focus the header of a new element which appears on the screen, for example when opening a modal.
IMPORTANT: This is Voiceover/Talkback functionality so you'll need to have that activated on your device.
See my snack: https://snack.expo.io/#insats/example-accessibilityinfo-setaccessibilityfocus-not-working
This is the code sample:
import React, { Component } from 'react';
import {
View,
Text,
findNodeHandle,
TouchableOpacity,
AccessibilityInfo,
StatusBar,
} from 'react-native';
class Sample extends React.Component {
constructor(props) {
super(props);
this.accessibilityRef = null;
}
componentDidMount() {
console.log('componentDidMount');
this.setAccessibilityFocus();
}
setAccessibilityRef(el) {
console.log('setAccessibilityRef', el);
this.accessibilityRef = el;
}
setAccessibilityFocus() {
console.log('setAccessibilityFocus', this.accessibilityRef);
if (this.accessibilityRef) {
const reactTag = findNodeHandle(this.accessibilityRef);
AccessibilityInfo.setAccessibilityFocus(reactTag);
}
}
render() {
console.log('Rendering Sample');
return (
<Text ref={this.setAccessibilityRef}>
This text ought to be read out loud by the screenreader if enabled
</Text>
);
}
}
export default class App extends React.Component {
state = {
open: false,
};
toggle = () => this.setState({ open: !this.state.open });
render() {
return (
<View style={{ margin: 50 }}>
<StatusBar hidden />
<TouchableOpacity
style={{ backgroundColor: 'blue', padding: 20, marginBottom: 20 }}
onPress={this.toggle}>
<Text style={{ color: 'white' }}>
{this.state.open ? 'Hide text' : 'Show text'}
</Text>
</TouchableOpacity>
{this.state.open && <Sample />}
</View>
);
}
}
I don't really understand what is causing these issues. I've found that calling the setAccessibilityFocus twice solves the problem. You can simplify the logic of focusing by just handling everything in the callback ref as well.
Example:
export default () => {
const setInitFocus = (element: React.Component | null) => {
if (element == null) return;
const elementId = findNodeHandle(element);
if (elementId) {
AccessibilityInfo.setAccessibilityFocus(elementId);
AccessibilityInfo.setAccessibilityFocus(elementId);
}
};
return (
<TouchableOpacity
onPress={() => {}}
ref={setInitFocus}
>
<Text>Blah blah</Text>
</TouchableOpacity>
);
};
Here's your snack with those changes applied:
https://snack.expo.io/#loganlim/example-accessibilityinfo-setaccessibilityfocus-not-working

I want to change background of a more than one child on press of one of the special child

Hello I have just started to learn react native, and i am stuck at this. I am not sure this the right way to do or need to change. Please help.
I have a parent component and a child component. Parent contain flatlist and rendering child. If a child is special then want to change background of multiple child, if not then only his background will change.
class Parent extends Component {
constructor(props){
a = ['a','h','n','1','2','3'];
this.state={
list=a,
};
}
render(){
return (
<View>
<FlatList
data={this.state.list}
renderItem={(item) => (
<Child name={item['item']} />)} />
</View>
)
}
}
class Child extends Component {
constructor(props){
this.state={
itemState:"off"
}
pressed(name){
//if name is alpha it's special change background of multiple.
//else change background of this only.
}
getImage(){
if(this.state.itemState === "on){
return require('onImgPath')
else
return require('offImgPath')
}
render(){
var imgp=this.getImage();
return (
<TouchableOpacity onPress={this.pressed.bind(this,this.props.name)>
<ImageBackground source={imgp}>
<Text>{this.props.name}</Text>
</ImageBackground>
</TouchableOpacity>
)
}
}
on the press of flatlist item i.e. child i want to change image. which will be based on name of child if its 'a' then all non special child will change background to 'on'. if 'h' then only first half will change to 'on' and other to 'off' and if it's 'n' then all non special will change to off image.
Please advise how to make it work and which way should be proper to handle such kind of situation.
You should precise :
if the name is alpha it's special change background of multiple.
Correction
This is a possible correction to your code :
import React, { Component } from 'react';
import { StyleSheet, View, FlatList, Text, ImageBackground,TouchableOpacity } from 'react-native';
export default class Parent extends Component {
constructor(props){
super(props);
let a = ['a','h','n','1','2','3'];
this.state={
list:a,
};
}
_renderItem = ({ item, index }) => (<Child name={item} /> );
render(){
return (
<View style={styles.container}>
<FlatList
data={this.state.list}
renderItem= {this._renderItem}/>
</View>
)
}
}
class Child extends Component {
constructor(props){
super(props);
this.state={
itemState:true,
}
}
pressed = () => {
if(this.props.name === 'alpha'){
// Do stuffs
}
this.setState({itemState:!this.state.itemState});
console.log(this.props.name);
}
render(){
return (
<TouchableOpacity style={styles.container} onPress={()=>this.pressed()} style={{ width: '100%', height: 80 }}>
<ImageBackground source={this.state.itemState === true ? require('assets/on.png') : require('assets/off.png')} style={styles.image}>
<Text style={styles.text}>{this.props.name}</Text>
</ImageBackground>
</TouchableOpacity>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
text : {
fontSize:16,
marginLeft:20,
color:'white'
},
image : { width: '100%', height: '100%' }
})
Start screen
Clicked on 'n' and '2'
Conclusion
you must call 'super' in the constructor
be aware to close all bracket opened: {}
look at the difference with your code, learn and enjoy!
A more precise answer
If I look to the question in the title of this thread, one solution is to control the state of your children in the parent component. You must think of the behaviour of the children in this case.
This is a solution :
import React, { Component } from 'react';
import { StyleSheet, View, FlatList, Text, ImageBackground,TouchableOpacity } from 'react-native';
let a = [
{name :'a',itemState: true,linked:['n','1']},
{name :'h',itemState: true},
{name :'n',itemState: true},
{name :'1',itemState: true},
{name :'2',itemState: true,linked:['h']},
{name :'3',itemState: true}
];
export default class Parent extends Component {
constructor(props){
//Never forget to call super()
super(props);
this.state={
list:a,
listLinked : []
};
}
//Don't forget to pass the callBack function
//This callBack process the state of items in list
_renderItem = ({ item, index }) => (<Child item={item} callBack={this._callBack}/> );
_callBack = (entrie) => {
//Look if item has linked elements and do the job
if (undefined != entrie.item.linked){
for(var i=0; i< entrie.item.linked.length; i++){
var indexLinkedItem = a.findIndex(element => element.name===entrie.item.linked[i]);
//Here you must think to the behaviour of your function
//By default, I toggle the actuel state of the linked item
//If you want to force them to be true or false
//You have to write :
// a[indexLinkedItem].itemState = true
//or
// a[indexLinkedItem].itemState = false
a[indexLinkedItem].itemState = !a[indexLinkedItem].itemState;
}
}
//In all case, the state of the pressed item change
var indexItem = a.findIndex(element => element.name===entrie.item.name);
a[indexItem].itemState = !a[indexItem].itemState;
//Assign the new list a to the state
this.setState({list : a});
}
render(){
console.log('main');
return (
<View style={styles.container}>
<FlatList
data={this.state.list}
renderItem= {this._renderItem}
//Use extraData to be sure flatlist rerender after _callBack
extraData={this.state}/>
</View>
)
}
}
class Child extends Component {
//This is a stateless component
pressed = () => {
this.props.callBack(this.props);
}
render(){
return (
<TouchableOpacity style={styles.container} onPress={()=>this.pressed()} style={{ width: '100%', height: 80 }}>
<ImageBackground source={this.props.item.itemState === true ? require('assets/on.png') : require('assets/off.png')} style={styles.image}>
<Text style={styles.text}>{this.props.item.name}</Text>
</ImageBackground>
</TouchableOpacity>
)
}
}
//This is the style you can change
//ImageBackground must have size
const styles = StyleSheet.create({
container: {
flex: 1,
},
text : {
fontSize:16,
marginLeft:20,
color:'white'
},
image : { width: '100%', height: '100%' }
})
From the start screen, when I click on the first item 'a', the state of the linked items 'n' and '1' change too :
Conclusion
You must think about what happens if I click on 'n' now?
Does it change only the state of 'n' or the state of 'a', 'n' and '1'?
And what happens if you have another item linked with 'a', 'n' or '1'?
Watch the code in action here :
https://www.youtube.com/watch?v=tmsfhZ53zNE&feature=youtu.be

react-native componentWillUnmount not working while navigating

I am doing this simple steps but unmount was not calling I don't know why. Please I need a solution for this I need unmount to be get called while navigating to another screen...
class Homemain extends Component {
constructor(props) {
super(props);
}
componentWillMount(){
alert('willMount')
}
componentDidMount(){
alert('didMount')
}
componentWillUnmount(){
alert('unMount')
}
Details = () => {
this.props.navigation.navigate('routedetailsheader')
}
render() {
return(
<View style={styles.container}>
<TouchableOpacity onPress={() => this.Details()} style={{ flex: .45, justifyContent: 'center', alignItems: 'center', marginTop: '10%', marginRight: '10%' }}>
<Image
source={require('./../Asset/Images/child-notification.png')}
style={{ flex: 1, height: height / 100 * 20, width: width / 100 * 20, resizeMode: 'contain' }} />
<Text
style={{ flex: 0.5, justifyContent: 'center', fontSize: width / 100 * 4, fontStyle: 'italic', fontWeight: '400', color: '#000', paddingTop: 10 }}>Details</Text>
</TouchableOpacity>
</View>
);
}
}
export default (Homemain);
This is my RouteConfiguration in this way I am navigating to the next screen. Can someone please help me for this error so that i can proceed to the next steps
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { addNavigationHelpers, NavigationActions } from 'react-navigation';
import { connect } from 'react-redux';
import { BackHandler } from 'react-native';
import { Stack } from './navigationConfiguration';
const getCurrentScreen = (navigationState) => {
if (!navigationState) {
return null
}
const route = navigationState.routes[navigationState.index]
if (route.routes) {
return getCurrentScreen(route)
}
return route.routeName
}
class StackNavigation extends Component {
static propTypes = {
dispatch: PropTypes.func.isRequired,
navigation: PropTypes.shape().isRequired,
};
constructor(props) {
super(props);
BackHandler.addEventListener('hardwareBackPress', this.backAction);
}
//backAction = () => this.navigator.props.navigation.goBack();
backAction = () => {
const { dispatch, navigation } = this.props;
const currentScreen = getCurrentScreen(navigation)
if (currentScreen === 'Homemain') {
return false
}
else
if (currentScreen === 'Login') {
return false
}
dispatch(NavigationActions.back());
return true;
};
render() {
const { dispatch, navigation } = this.props;
return (
<Stack
ref={(ref) => { this.navigator = ref; }}
navigation={
addNavigationHelpers({
dispatch,
state: navigation,
})
}
/>
);
}
}
export default connect(state => ({ navigation: state.stack }))(StackNavigation);
Routing to a new screen does not unmount the current screen.
For you usecase you instead of writing the code in componentWillUnmount you can continue by writing it after calling navigate in Details itself.
If you are looking for a callback when you press back from the new screen to come back to the current screen. Use goBack as shown in https://github.com/react-navigation/react-navigation/issues/733
If you are using a stack navigator, then routing to a new view loads the new view above the old one. The old view is still there for when you navigate back.
As I understand from your question and code you are using redux with navigation and want to unmount a screen. So what I did I just added a screen component inside another component to make my screen component as child.
e.g. below is the snippet that I am using to unmount the PushScreen from PushedData component.
I render PushScreen and inside it there is component PushedData that originally making the view. On PushedData `componentWillMount I am just doing some conditional functionality and on success I am just unmounting PushData from PushScreen.
class PushScreen extends Component{
state ={ controllerLaunched: false };
updateControllerLauncher = () => {
this.setState({ controllerLaunched: true });
}
render (){
if(this.state.controllerLaunched){
return null;
} else {
return <PushedData handleControllerLauncher={this.updateControllerLauncher} />;
}
}
}
class PushedData extends Component{
componentWillMount(){
this.unmountPushData();//calling this method after some conditions.
}
unmountPushData = () => {
this.props.handleControllerLauncher();
}
render(){
return (
<View><Text>Component mounted</Text></View>
);
}
}
Let me know if you need more information.
When you use Stack Navigator then routing to a new view loads the new view above the old one as Rob Walker said. There is a workaround. You can bind blur event listener on componentDidMount using navigation prop:
componentDidMount() {
this.props.navigation.addListener('blur', () => {
alert('screen changed');
})
}
So when your screen goes out of focus the event listener is called.
You can find more about events right here.
If you want to go next Screen use (.replace instead .navigate) where you want to call componentWillUnmount. and if you want to go back to one of previous screens use .pop or .popToTop.
you can set condition for costume function that u write for hardware button , for example when ( for example for React Native Router Flux ) Actions.currentScene === 'Home' do something or other conditions u want .

How to redirect to a page in React Native?

this.props.navigator.push({
component: MainView,
passProps: {selectedTab: 'home', message: 'this is the messages'},
});
Is this the only way to redirect page? That said, when condition hit, I want to render another page, what is the right way to do in React Native?
There are quite a few different ways to do redirect the page. From the React Native docs, here are the methods available to Navigator:
push(route) - Navigate forward to a new route
pop() - Go back one page
popN(n) - Go back N pages at once. When N=1, behavior matches pop()
replace(route) - Replace the route for the current page and immediately load the view for the new route
replacePrevious(route) - Replace the route/view for the previous page
replacePreviousAndPop(route) - Replaces the previous route/view and transitions back to it
resetTo(route) - Replaces the top item and popToTop
popToRoute(route) - Go back to the item for a particular route object
popToTop() - Go back to the top item
To change view based on a condition, you can have a function that calls this.props.navigator to one of the above actions in the componentWillMount and componentWillUpdate, calling the function if a state variable changes.
I've built a basic demo here demonstrating this. (try replacing .replace with .push to see other common functionality) The code is also below (note the changeView function).
https://rnplay.org/apps/qHEJxQ
"use strict";
var React = require("react-native");
var {
AppRegistry,
StyleSheet,
NavigatorIOS,
Component,
TouchableHighlight,
StyleSheet,
View,
Text
} = React;
var project = React.createClass({
render: function() {
return (
<NavigatorIOS
style={{flex:1}}
initialRoute={{
component: ProfileView,
title: 'ProfileView'
}}
/>
);
}
});
var styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
}
});
AppRegistry.registerComponent("project", () => project);
var LoginView = React.createClass({
render: function() {
return(
<View style={{flex:1}}>
<Text style={{marginTop:100}}>Hello from LOGIN VIEW</Text>
</View>
)
}
})
class ProfileView extends Component {
constructor (props) {
super(props);
this.state = {
signedin: false
};
}
componentDidUpdate() {
if(this.state.signedIn) {
this.props.navigator.replace({
component: LoginView,
title: 'LoginView',
})
}
}
componentDidMount() {
if(this.state.signedIn) {
this.props.navigator.replace({
component: LoginView,
title: 'LoginView',
})
}
}
changeView() {
this.setState({
signedIn: true
});
}
render () {
return (
<View style={styles.container}>
<Text style={{marginTop:200}}>
Welcome
</Text>
<TouchableHighlight onPress={ () => this.changeView() } style={{height:50, flexDirection: 'row', justifyContnet: 'center',backgroundColor: '#ddd'}}>
<Text style={{fontSize:20}}>Sign In</Text>
</TouchableHighlight>
</View>
);
}
};
var styles = StyleSheet.create({
container: {
flex: 1,
}
});
module.exports = ProfileView;
You can do something like this in your initial route page -
renderScene(route, navigator) {
switch (route.id) {
case 'first-page':
var AppFirstPage = require('./app/components/FirstPage');
return <AppFirstPage navigator={navigator} route={route} user={this.data.user}/>;
break;
case 'login-page':
var LoginPage = require('./app/components/LoginPage');
return <LoginPage navigator={navigator} route={route}/>;
break;
case 'signup-page':
var SignUpPage = require('./app/components/SignUpPage');
return <SignUpPage navigator={navigator} route={route}/>;
break;
}
},
and now whenever you want to go to a specific route, you can do -
this.props.navigator.push({
id: 'signup-page',
sceneConfig: Navigator.SceneConfigs.FloatFromBottom
})
Hope this helps. Cheers!