I'm trying to get navigating working with react-native. I am testing it with 2 pages (HomePage & OtherPage) and want HomePage to be able to nav to OtherPage when I submit some text.
My basic navigator index.android.js file...
class MyApp extends Component {
_renderScene = function(route, nav) {
switch (route.index) {
case 'HomePage':
console.log('homepage nav')
return <HomePage navigator={nav} />
case 'OtherPage':
console.log('otherpagenav')
return <OtherPage navigator={nav} />
}
}
render() {
return (
<Navigator
initialRoute={{ title: 'My Initial Scene', index: 'HomePage' }}
renderScene={ (route, nav) => { return this._renderScene(route, nav) } }
/>
)
}
}
Both pages HomePage and OtherPage work if I set them manually via the index and initialRoute.
initialRoute={{ title: 'My Initial Scene', index: 'HomePage' }}
or
initialRoute={{ title: 'My Initial Scene', index: 'OtherPage' }}
My HomePage.js file which is loaded fine, looks like...
class HomePage extends Component {
constructor(props) {
super(props);
}
_onSubmit() {
console.log( 'submit2', this);
this.props.navigator.push({
title: "Other Page",
index: 'OtherPage',
component: OtherPage,
passProps: { testprop: 'someuser'},
});
}
render() {
return (
<View>
<Text>Current Scene: {this.props.title}</Text>
<TextInput
onChangeText = {( keywords ) => this.setState({ keywords })}
defaultValue = 'Type a keyword'
onSubmitEditing = { this._onSubmit.bind( this ) }
/>
</View>
)
}
}
My Test OtherPage is just
class OtherPage extends Component {
constructor( props ) {
super( props );
}
componentDidMount() {
console.log( 'otherpage component mounted');
}
render() {
return (
<View><Text>Other Page</Text>
</View>
);
}
}
When I enter some text and submit. I see the submit console.log from _onSubmit() So I know the navigator is getting called ok.
I also see 'otherpage component mounted'.
So everything looks ok log wise, but I don't see the scene/page updated, all I see is the updated console.logs.
Is there something I need to do to force it to update the scene, or shouldn't it happen automatically with the navigator push ? (I'm also a little unsure of the need for navigator component, as I've seen it in some example, but not really referenced anywhere).
Looks like there was something odd with the avd. I ended up upgrading react-native to 0.39.2 (which I don't think made a difference, but including for completeness). Then I deleted the avd, created a mew one (target name Android 6.0 Platform 6.0 API 23 Intel Atom x86_64) selected delete all user data, and then it seems to work.
Wish I knew specifically why, so if anyone has a better answer, I will gladly accept.
Related
My application scenario is like, let say you have three components:
class ComponentOne extends Component {
render() {
return (
<View>
<Text>Component One</Text>
<Button
title='Go To Component Two'
onPress={() => this.props.navigation.navigate('two')}/>
</View>
);
}
}
class ComponentTwo extends Component {
render() {
return (
<View>
<Text>Component Two</Text>
<Button
title='Go To Component Three'
onPress={() => this.props.navigation.navigate('three')}/>
</View>
);
}
}
class ComponentThree extends Component {
render() {
return (
<View>
<Text>Component Three</Text>
<Button
title='Go To Component One'
onPress={() => this.props.navigation.navigate('one')}/>
</View>
);
}
}
export default createStackNavigator({one: ComponentOne,two: ComponentTwo,three: ComponentThree});
Now when I load the app the ComponentOne will be loaded, inside the ComponentOne when I click on the button Go To Component Two it will navigate me to the ComponentTwo, inside ComponentTwo when I click on the button Go To Component Three it will navigate me to the ComponentThree and so on. Now let say I am in ComponentTwo and on the same time I close the application in the phone and I open the app switcher and clean all the running apps and load the same app again, so, it will be again loaded with ComponentOne.
My question is how to program the react navigation to continue from the same component where last time I left, even after cleaning the app from a background (cleaning all apps in app switcher)?
Is there any builtin way in react navigation to do this? Can anyone tell? Examples will be more appreciated. Thanks!
All Navigators have onNavigationStateChange where you can handle the navigation state changing. Example code:
import React from 'react';
import { AsyncStorage } from 'react-native';
import { createStackNavigator, NavigationActions } from 'react-navigation';
const Navigator = createStackNavigator({
ComponentOne: {
screen: ComponentOne,
},
ComponentTwo: {
screen: ComponentTwo,
},
ComponentThree: {
screen: ComponentThree,
},
}, {
initialRouteName: 'ComponentOne',
});
class App extends Component {
constructor(props) {
this.navigator = React.createRef();
}
componentDidMount() {
try {
// Retrieve the last route
const value = AsyncStorage.getItem('lastNavigationRoute').then((result) => {
if (result) {
this.navigator.current.dispatch(NavigationActions.navigate({
routeName: result,
}));
}
});
} catch (e) {
// handle the error
}
}
handleStateChange = (previousState, nextState) => {
// Here we get the Navigate action type only
const navigateAction = NavigationActions.navigate({ routeName: 'dummyRoute' });
if (action.type === navigateAction.type) {
try {
// Saving the last route
AsyncStorage.setItem('lastNavigationRoute', nextState.routeName);
} catch (e) {
// handle the error
}
}
}
render() {
// You could also set a state with loader to handle loading from AsyncStorage
return (
<Navigator onNavigationStateChange={this.handleStateChange} ref={this.navigator} />
);
}
}
How it works:
On every navigation state changing you also save the last routeName
from Navigate action
When component did mount, you check for saved
route in AsyncStorage
If there is a route, you dispatch the navigate action (it's possible to implement replace action as well)
Hope it helps.
i dont think that there is a solution directly using react-navigation.
What i think you could do is to save a value of the current screen to the storage of the phone and then use this value on app start to detect which screen to show
I'm dynamically creating components in create-react-native-app. Everything is working fine using the expo app for testing in Development mode using npm start, and connecting with an android phone.
If I switch it to Production mode, or try to build the apk as a Standalone app the object is not created on the Button press.
This is my first project with React Native, and I don't know how to debug this.
I've also been unable to find any information about what the differences between these two modes might be that would lead to this.
Here the relevant code:
export default class App extends React.Component {
constructor(props) {
super(props);
this.updateState = this.updateState.bind(this);
this.state = {
knobs: [],
key: 1
}
}
add = () => {
let key = this.state.key + 1
let knob = (<Object key={key} updateState={this.updateState}/>);
let knobs = this.state.knobs;
knobs.push(knob);
this.setState({knobs: knobs, key: key})
}
render = () => {
return ([<View>
{this.state.knobs}
<Button onPress={() => this.add()} title='add thing'/>
</View>
]);
}
}
I'm not sure what causes the issue since we don't have any sort of error message but below snippet of code might help.
When you assign a variable like below;
let knobs = this.state.knobs;
You are not creating a new variable, you are creating a reference to the original property. Because of this you mutate the state. This might cause the issue.
For setting new state values related to current state values you can use functional setState syntax and destructuring assignment. It is a little bit more easy to use and a little bit more easy to read.
add = () => {
this.setState((prevState) => {
const { knobs, key } = prevState; // deconstruct array and key from state
const newKnob = (<Object key={(key + 1)} updateState={this.updateState}/>);
knobs.push(newKnob); // push new item to array
return { knobs, key: (key + 1) } //return new state values
});
}
Oh, so in the end I rewrote the whole bit.
Moving the objects to be created into the render function.
export default class App extends React.Component {
constructor() {
super();
this.state = {
things: []
}
this.key = 0;
}
add = () => {
let addThing = { key: this.key }
this.setState({ things: [ ...this.state.things, addThing ] })
this.key = this.key + 1;
}
render() {
let newThings = this.state.things.map((key) => {
return (
<Text key={key}>New Thing.</Text>
);
});
return (<View style={styles.container}>
{newThings}
<Button onPress={() => this.add()} title='add thing'/>
</View>);
}
}
This functions as expected in Production mode and as an App;)
Hi I am trying to navigate to next component using navigate function. I am using react-navigation for the navigation among multiple components.
Suppose I have index.android.js and DashboardScreen.js component. I am trying to navigate to DashboardScreen.js component from index component.
It is navigating but index component always retain in component stack. when I press back then it opens index.android.js which should not be. Does anyone know how to manage this in react-native. In Android, finish() works for this.
navigate("DashboardScreen");
When I am navigating from SplashScreen to EnableNotification then SplashScreen should be destroyed, if I am navigating from EnableNotification to CreateMessage then EnableNotification should be destroyed and if I am navigating from CreateMessage to DashboardScreen then CreateMessage should be destroyed. As of now no component is being destroyed.
index.android.js
class SplashScreen extends Component {
render() {
if (__DEV__) {
console.disableYellowBox = true;
}
const { navigate } = this.props.navigation;
AsyncStorage.getItem("#ProductTour:key").then(value => {
console.log(value);
if (value) {
navigate("DashboardScreen");
}
});
return (
....
);
}
}
const App = StackNavigator(
{
Splash: {
screen: SplashScreen,
navigationOptions: {
header: {
visible: false
}
}
},
EnableNotification: {
screen: EnableNotificationScreen,
navigationOptions: {
header: {
visible: false
}
}
},
CreateMessage: {
screen: CreateMessageScreen,
navigationOptions: {
header: {
visible: false
}
}
},
DashboardScreen: {
screen: DashboardScreen,
navigationOptions: {
header: {
visible: false
}
}
}
},
{
initialRouteName: "Splash"
}
);
Just use 'replace' in place of 'navigate'
this.props.navigation.replace('Your Next Component Name')
First of all, using AsyncStorage in an a synchronous function (most especially a lifecycle one) is such a bad idea. You should typically keep ASyncStorage to places in your folder / app structure that make sense for where you access/keep data but since that's not the question I will just mention it quickly here...
Basically you are asking to navigate once the ASync method completes itself based on EVERY render... Those new to RN should know that an awful lot of things can cause a render to fire. Some cases, the render function can fire (I have seen this many times before) 10 or more times before finalizing the last render. This means you would have fired that ASyncStorage method 10 times... definitely something to think about when implementing this stuff. So more or less, the .then(); part of the AsyncStorage function is firing long after the render has already finished doing it's thing. If it was a reasonable approach to use I would say to put the return part of the render function inside of the .then((value) => { return ( ... ); });. But this is an even worse idea. Basically you need the right lifecycle method here and it's NOT the render method.
Anyway, since I have never used this component library before I can only help nudge you in the right direction so here goes... These docs on their webpage seem to say that you need a reference to the props navigator passed down to the component in which you are using it. So if you created the navigator in this class, you would use this.refs.whateverYouNamedTheNavigatorReference.navigate('SomeItemName'). If you are in the class that has been passed this navigator as a prop, you use this.props.passNavigatorPropName.navigate('SomeItemName'). I see you are using variable deconstruction to get the navigate callback but I would caution on doing this, this way because I have seen it cause errors by grabbing an old version of the navigate function or its parent reference by accident and causing a cascading error effect.
Also, if you are going to be using ASyncStorage in a component file (again, would recommend putting this in a component/class where your data is accessed throughout the app...) and you are going to use it to decide the app should navigate forwards/backwards... definitely remove it from the render function and put it in maybe the constructor, componentWillReceiveProps, componentDidReceiveProps or componentWillUpdate lifecycle functions. That way it fires based on an update, a new passed prop obj or one time as the component is built. Anything is better than firing it every single render.
Lastly, I do not know what you have setup for your StackNavigator route stack object but you would need to have the keyword you used "DashboardScreen" in there pointing to an actual component that has been imported properly. The "DashboardScreen" keyword most likely would connect in your StackNavigator object to some component import like so...
import Dashboard from '../Views/DashboardScreenView';
StackNavigator({
DashboardScreen: {
screen: Dashboard,
path: 'dashboard/:main',
navigationOptions: null,
},
});
There is a simple way here: use "replace" (reference link repleace in navigation ,For example, you are at the screen "Login" ,
and you want to move to screen "Home", insert this code in screen "Login"
<TouchableOpacity onPress={() => { this.login() }}>
<Text}>Click me to Login</Text>
</TouchableOpacity>
and method login:
login(){
this.props.navigation.replace('Home')
}
Screen "Login" will be replaced by "Home", in Android, press Back Button =>app exit, no back screen "Login"
Based on your requirement, i suggest following setup:
SplashNavigator.js
const SplashNavigator = StackNavigator({
Splash: {
screen: SplashScreen,
navigationOptions: {
header: {
visible: false
}
}
}
});
AppNavigator.js
const AppNavigator = StackNavigator(
{
EnableNotification: {
screen: EnableNotificationScreen,
navigationOptions: {
header: {
visible: false
}
}
},
CreateMessage: {
screen: CreateMessageScreen,
navigationOptions: {
header: {
visible: false
}
}
},
Dashboard: {
screen: DashboardScreen,
navigationOptions: {
header: {
visible: false
}
}
}
},
{
initialRouteName: "EnableNotification"
}
);
In your index.android.js, you will render the SplashNavigator.
The SplashNavigator will render the SplashScreen. It has initial state value isReady set to false, so it will render a loading text until the #ProductTour:key value from AsyncStorage is loaded (AsyncStorage is async function, u should not put it in your render function). It will then render your AppNavigator and render your EnableNotification as initial route.
class SplashScreen extends Component {
constructor() {
super(props);
this.state = {
isReady: false,
}
}
componentDidMount() {
AsyncStorage.getItem("#ProductTour:key").then(value => {
console.log(value);
// you will need to handle case when `#ProductTour:key` is not exists
this.setState({
isReady: true,
});
});
}
render() {
const { isReady } = this.state;
return (
<View style={{flex: 1}}>
{
isReady ?
<AppNavigator />
: <Text>Loading</Text>
}
</View>
);
}
}
Then on EnableNotificationScreen and CreateMessageScreen, change your navigate route function to use NavigationActions.reset from doc
Example:
import { NavigationActions } from 'react-navigation';
handleOnPressButton = () => {
const resetAction = NavigationActions.reset({
index: 0,
actions: [
NavigationActions.navigate({ routeName: "CreateMessage" })
]
});
this.props.navigation.dispatch(resetAction);
}
Yes in react native you can finish the current screen before navigating to new screen with the help of NavigationActions . Please refer this link -
http://androidseekho.com/others/reactnative/finish-current-screen-on-navigating-another-in-react-native/
SplashNavigator.js
const SplashNavigator = StackNavigator({
Splash: {
screen: SplashScreen,
navigationOptions: {
header: null}
}
}
});
Import StackActions and NavigationActions from react-navigation.
import { StackActions, NavigationActions } from 'react-navigation';
below code for performing Action
navigateToHomeScreen = () => {
const navigateAction = StackActions.reset({
index: 0,
actions: [NavigationActions.navigate({ routeName: "HomeScreen" })],
});
this.props.navigation.dispatch(navigateAction);
}
Sorry if there is an obvious solution to this but I don't find the documentation for react-navigation to be very clear which is frustrating as it's now being presented as the official react-native navigation library.
In my index file I call asynstorage to retrieve an access token generated on login. Then I want to either return a protected stack if logged in or the welcome stack if not.
render() {
if (this.state.isLoggedin === true) {
return <Protected />;
}
else {
return <Root />;
}
}
Here is my router
export const Root = StackNavigator({
Welcome: { screen: Welcome },
Login: { screen: Login },
Register: { screen: Register }
});
export const Protected = StackNavigator({
Conversations: { screen: Conversations }
});
This is working when I reload the app. What I don't understand is how to navigate from "Root" to "Protected" on press, I don't want these routes in the same StackNavigator. Is there a way to do this or do I need to change the entire structure and have both Root and Protected under one stacknavigator?
Again sorry if this is obvious but i've built a chat app using the react native Navigator before it was deprecated and didn't find it nearly as confusing. I feel that there should be better documentation and examples of how a real world app should be structured.
you can navigation a other page with this.props.navigation.navigate('otherView'), for example review, that next example navigato to a page Chat
App.js
class ChatScreen extends React.Component {
static navigationOptions = {
title: 'Chat with Lucy',
};
render() {
return (
<View>
<Text>Chat with Lucy</Text>
</View>
);
}
}
class HomeScreen extends React.Component {
static navigationOptions = {
title: 'Welcome',
};
render() {
const { navigate } = this.props.navigation;
return (
<View>
<Text>Hello, Chat App!</Text>
<Button
onPress={() => navigate('Chat')}
title="Chat with Lucy"
/>
</View>
);
}
}
this routes was defined, in this have Chat a where want navigate
const SimpleApp = StackNavigator({
Home: { screen: HomeScreen },
Chat: { screen: ChatScreen },
});
Warning if you are doing a login verify the subject that after being logged in the user could give back and return to the login, it would be advisable to reset the routes when logging in
this.navigation.dispatch(NavigationActions.reset(
{
index: 0,
actions: [
NavigationActions.navigate({ routeName: 'Home', params: {param: data},})
]
}));
I totally agree with you, React Navigation's documentation can be very confusing. Unforutnately Navigators don't provide a direct way to pass down props to child components. For this case, you unfortunately need to use an escape hatch, basically a common module that allows you to connect the components to each other. Instead of advising you to write this complicated component on your own, I would strongly suggest using redux. Even though it requires some boilerplate but it would allow you to connect all components to the same state, so in your main component class the isLoggedIn variable would immediately change when the Login or Register components change it using redux actions. I would suggest reading the redux documentation and getting started with it. You basically create a redux store with just the user authentication in it and go from there.
I've been struggling with a weird issue in switch components in React Native when running inside Android app.
Lets say, I have a component which render method looks like this:
render() {
return (
<View>
<View>
<Text>
Test Title
</Text>
<Switch
value={ this.state.value }
onValueChange={
this.test.bind( this )
}
/>
</View>
</View>
);
}
The test method is:
constructor(props){
super(props);
this.state = {
value: true
};
}
test(){
this.setState( {value: !this.state.value})
}
When I run my module inside my iOS app the onValueChange method gets called and everything works as expected, however, when I do the same in my Android app the method never gets called when the value is changed to false. What is more, I cannot change the value more than once i.e I can only set the value to false and it will not allow me to set it to true afterwards. The only way I can play with the switch element again is by holding the bar, nonetheless, the value never gets changed (The switch component doesn't change its color) nor the method called .
Has anyone faced something similar? Is this a issue with RN and its Switch component for Android?
I am using:
react: 15.4.1
react-native: 0.39
***NOTE 1: The onValueChange gets called when I put my RN code inside an activity but it fails when it's inside a fragment.
Try This.
constructor(props){
super(props);
this.state = {
value: true
};
}
and in your render
render() {
return (
<View>
<Text>
Test Title
</Text>
<Switch
value={ this.state.value }
onValueChange={(value) => this.setState({value})}
/>
</View>
);
}
You can remove your test() function
what works for me is this,
constructor(props) {
super(props)
this.state = {
isOpen : false
}
this.onControlChange = this.onControlChange.bind(this);
}
onControlChange(value) {
return this.setState({
isOpen: !this.state.isOpen
});
}
and in return use this way
render() {
return (
<Switch
onValueChange={this.onControlChange}
value={this.state.isOpen}
/>
)
}
so i believe that you should declare binding for your function in constructor. I tested this for Android emulator only.
Hope this helps.