I have been working on this problem for two days now and nothing on the web seems to be exactly what I am looking for.
I am attempting to implement a StackNavigator into my React Native app, but for some reason "navigation" is not being passed as a prop to the involved components. Therefore when I call this.props.navigation.navigator by pressing Button, I get the error undefined is not an object (evaluating this.props.navigation.navigate).
I have logged the props several times and the props object is empty, so the issue is not a deconstruction-of-the-props-object issue like others who get this error have had, but the fact that the navigation prop is not there in the first place.
I've tried placing the navigator code in its own file and in the App.js file thinking that it was somehow called after the components are rendered, and therefore not getting a chance to pass the navigation prop in, but that didn't work either. I've also looked to see if it is part of the props in the componentDidMount event. Still not.
import React, { Component } from 'react'
import { Text, View, Button, StyleSheet, FlatList } from 'react-native'
import { StackNavigator } from 'react-navigation'
import { getDecks } from '../utils/api'
import NewDeckView from './NewDeckView'
import DeckListItem from './DeckListItem'
export default class DeckListView extends Component {
constructor(props){
super(props)
this.state = {
decks: []
}
}
componentDidMount(){
console.log('props now test',this.props)
getDecks()
.then( result => {
const parsedResult = JSON.parse(result);
const deckNames = Object.keys(parsedResult);
const deckObjects = [];
deckNames.forEach( deckName => {
parsedResult[deckName].key = parsedResult[deckName].title
deckObjects.push(parsedResult[deckName])
})
this.setState({
decks:deckObjects
})
} )
}
render(){
return (
<View style={styles.container}>
<Text style={styles.header}>Decks</Text>
<FlatList data={this.state.decks} renderItem={({item})=><DeckListItem title={item.title} noOfCards={item.questions?item.questions.length:0}/>} />
<Button styles={styles.button} title="New Deck" onPress={()=>{this.props.navigation.navigate('NewDeckView')}}/>
</View>
)
}
}
const styles = StyleSheet.create({
header:{
fontSize:30,
margin:20,
},
container:{
flex:1,
justifyContent:'flex-start',
alignItems:'center'
},
button:{
width:50
}
})
const Stack = StackNavigator({
DeckListView : {
screen: DeckListView,
},
NewDeckView: {
screen:NewDeckView,
}
})
Like Vicky and Shubhnik Singh mentioned, you need to render the imported navigation stack in App.js like so:
import React from 'react';
import { Stack } from './navigator/navigator'
export default class App extends React.Component {
render() {
return <Stack/>
}
}
The navigator should look something like this and the first key in the object passed to StackNavigator will be rendered by default. In this case, it will be DeckListView.
import { StackNavigator } from 'react-navigation'
import DeckListView from '../components/DeckListView'
import NewDeckView from '../components/NewDeckView'
export const Stack = StackNavigator({
DeckListView : {
screen: DeckListView,
navigationOptions: {
headerTitle: 'Home',
},
},
NewDeckView: {
screen:NewDeckView,
navigationOptions: {
headerTitle: 'New Deck',
},
},
})
Thanks guys for the support! Somehow this wasn't clear for me in the documentation.
Related
Every little help is appreciated very much!
I got an Avatar as a Navigator, which should allow me to navigate through some screens! Some other Screens should be reached only by the my home screen. But when I press on the Button in the Avatar, nothing happens. I don't understand how and why, but I believe I made a mistake in the stackNavigator somewhere? Just to avoid any misunderstanding, the code below is shortened to avoid filling the page with unnecessary information, so it doesn't reflect 1:1 what you see in the screenshots.
Home Screen which should change
Avatar Screen through which I should be able to change the Home Screen
The goal is to be able to change which Screen is displayed in the <AppContainer/> through the <Avatar/> component below.
I tried with a onPress function inside the Avatar.js to call this.props.navigation.navigate("Agenda") but the screen didn't change. At least I got rid of the errors thanks to passing the navigation prop. I believe I made kind of a scrub mistake since i'm not experienced with react native, I hope some of the more experienced users can help me with that :) furthermore I don't think the screen itself is important to solve this, it's just a class component with panresponder and animated view.
import React from "react";
import { StyleSheet, View, StatusBar } from "react-native";
import { createAppContainer } from "react-navigation";
import { createStackNavigator } from "react-navigation-stack";
import Avatar from "../coreComponents/Avatar";
import ScHome from "../screens/ScHome";
import ScNewMedi from "../screens/ScNewMedi";
import ScCalendar from "./ScAgenda";
const AppStack = createStackNavigator(
{
Home: {
screen: ScHome,
},
Medis: {
screen: ScNewMedi,
},
Agenda: {
screen: ScCalendar,
},
},
{
headerMode: "screen",
initialRouteName: "Home",
backBehavior: "order",
}
);
const AppContainer = createAppContainer(AppStack);
export default class ScDefault extends React.Component {
render() {
return (
<View style={styles.container}>
<StatusBar
barStyle="light-content"
backgroundColor="transparent"
translucent
/>
<AppContainer />
<Avatar navigation={this.props.navigation} />
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
});
The problem is you are trying to access navigation outside the Appcontainer where the navigation is not available.
Easy fix :
Moving the Avatar component inside one of your main screens(ScHome,ScNewMedi,ScCalendar) then you will have access to navigation and everything will work as expected.
Workaround :
Let say the requirement is to have the Avatar outside the AppContainer then you have to use something like a navigation service.
The code would be as below
let _navigator;
const setTopLevelNavigator=(navigatorRef) =>{
_navigator = navigatorRef;
}
const navigate=(routeName, params) =>{
_navigator.dispatch(
NavigationActions.navigate({
routeName,
params,
})
);
}
//Change your app.js
return <View style={{flex:1}}>
<AppContainer
ref={navigatorRef => {
setTopLevelNavigator(navigatorRef);
}}
/>
<Avatar navigate={navigate} /></View>;
//Inside the Avatar onPress you can can do this
onPress={()=>this.props.navigate("Home")}
You can refer the docs for more details
I am currently working on a app which works with react native and I tried to make a flow using react-navigation working on this tutorial but I am having trouble at the point of running my project, I've done a lot of research and i just cant get to the solution, first for all I am using react-native-cli: 2.0.1 and react-native: 0.48.3, this is my code:
App.js:
import React, { Component } from 'react';
import { Text, Button, View } from 'react-native';
import {
StackNavigator,
} from 'react-navigation';
class App extends Component {
static navigationOptions = {
title: 'Welcome',
};
render() {
console.log(this.props.navigation);
const { navigate } = this.props.navigation;
return (
<View>
<Text>Go to maps!</Text>
<Button
onPress={() => navigate('Map')}
title="MAP"
/>
</View>
);
}
}
export default App;
my Router.js
import {
StackNavigator,
} from 'react-navigation';
import MapMarker from './MapMarker';
import App from './App';
export const UserStack = StackNavigator({
ScreenMap:
{
screen: MapMarker,
navigationOptions:
{
title: "Map",
header:null
},
},
ScreenHome:
{
screen: App,
navigationOptions:
{
title: "Home",
headerLeft:null
},
},
});
As you can see this is a pretty basic App which i just cant make work, this is a screenshot of my error on the simulator:
I would really really appreciate if you guys could help me with this.
Thanks.
You should change the code onPress={() => navigate('Map')} to onPress={() => navigate('ScreenMap')} and ScreenHome should be the first screen then ScreenMap as you want to navigate from App to MapMarker. Or you can set initialRouteName: ScreenHome in the stacknavigator.
You create your StackNavigator, but where do you use it? You should have something like
import React, { Component } from 'react';
import {
AppRegistry,
View
} from 'react-native';
import {
StackNavigator,
} from 'react-navigation';
export default class MyApp extends Component {
render() {
return (
<View style={{flex:1}}>
<StackNavigator/>
</View>
);
}
}
AppRegistry.registerComponent('MyApp', () => MyApp);
Now that your StackNavigator is controlling what is shown, your App component will have navigation in its props. Note, you do not "pass" the navigation prop. This is handled for you.
Suppose I have this:
const navtest = StackNavigator({
Home: {screen: HomeScreen},
Stuff: {screen: Screen2},
Stuff2: {screen: Screen3},
});
And I'm currently viewing Stuff2/Screen3. And I have some function in my HomeScreen component, say XYZ( ). On the basis of any certain action on my current route ( Screen3 ), how do I call that function XYZ( ) in HomeScreen?
My Routes file which directly links to index.js.
import {AppRegistry} from 'react-native';
import React, { Component } from 'react';
import {StackNavigator} from 'react-navigation';
import Screen2 from './Screen2';
import Screen3 from './Screen3';
import HomeScreen from './HomeScreen';
const navtest= StackNavigator({
Home: {screen: HomeScreen},
Stuff: {screen: Screen2},
Stuff2: {screen: Screen3},
});
AppRegistry.registerComponent('navtest', () => navtest);
and in my HomeScreen file
export default class HomeScreen extends Component {
constructor(props){
super(props);
this.state = {one: '0'};
}
static navigationOptions = {
header: null,
};
functionToPass() {
console.log('Function accessed');
this.setState({one: 'X'});
}
render() {
const {navigate} = this.props.navigation;
return (
<View>
<TouchableOpacity onPress = {() => navigate('Stuff', {ABC: 'abc'})}>
<Text>
{this.state.one}
</Text>
</TouchableOpacity>
</View>
);
}
}
Are you navigating from Home to Screen3? Then you can pass you function down as a param to be available in the navigation state. E.g. this.props.navigation.navigate(‘Stuff2’, { someFunc: someFuncFromHome }); then, in your Screen3, you can invoke the function like this: this.props.navigation.state.params.someFunc();
If Home is not the previous route, then maybe the function should be extracted outside of the Home component and use a redux action or similar things to achieve the same result.
Follow up:
There are a few ways that I think that may be able to help you.
1) Introduce Redux to your app. This is a good use case for redux as this state can be manipulated from anywhere in the app. Dispatch the same redux action from any where in the app, and the HomeScreen can read from redux.
2) If you don't want redux in your app yet, maybe you can keep the state in a navigator wrapper component like the one in my comment earlier.
class App extends Component {
constructor(props) {
super(props)
this.state = { one: '0' };
}
yourRootChangeFunc() {
this.setState({ one: 'X' });
}
render() {
return <App screenProps={{ rootChange: this.rootChange, one: this.state.one }} />
}
}
Then you can access one in other screens with this.props.screenProps.one
3) Another way is use setParams, which you can setParams for a specific screen by passing in the key of the route.
In HomeScreen component:
export default class HomeScreen extends Component {
...
componentWillMount() {
this.props.navigation.setParams({ one: '0' });
}
render() {
const { state: { params = {} } = this.props.navigation;
return (
...
<View>
{params.one || '0'} // this is because currently there is no way to set a default params for a route
</View>
...
);
}
}
Then in Screen2 component:
import React, { Component } from 'react';
import {
View,
TouchableOpacity,
Text,
} from 'react-native';
import { NavigationActions } from 'react-navigation';
export default class Screen2 extends Component {
onPress = () => {
const setParamsAction = NavigationActions.setParams({
params: { one: 'X' },
key: this.props.navigation.state.params.homeKey,
})
this.props.navigation.dispatch(setParamsAction)
}
render() {
return (
<View>
<TouchableOpacity onPress={this.onPress}>
<Text>Screen2</Text>
</TouchableOpacity>
</View>
);
}
}
This way, pressing on Screen2 text in Screen2 will change the params for HomeScreen. Then when go back to HomeScreen, we can see that the text changed from '0' to 'X'.
New react native user here. I'm running into an issue and I am not sure how to proceed. I was able to get react-navigation running properly and then began receiving an error: "The component for route must be a a React Component" but unless I'm missing something, I believe that the component I am referencing is a react component. See my index.android.js below and my Home.js below:
//index.android.js
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View
} from 'react-native';
import {
TabNavigator,
StackNavigator
} from 'react-navigation';
import Home from './app/components/Home/Home';
import Search from './app/components/Search/Search';
export default class demoApp extends Component {
render() {
return (
<SimpleNavigation/>
);
}
}
export const SimpleNavigation = StackNavigator({
Home: {
screen: Home,
header: { visible: false },
navigationOptions: {
title: 'Home',
header: null
},
},
Search: {
screen: Search,
navigationOptions: {
title: 'test'
},
},
},{});
//Home.js
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
Image,
TextInput,
Button,
TouchableHighlight
} from 'react-native';
class Home extends Component {
constructor(props){
super(props);
this.state = {zipCode: ''}
}
navigate = (zipCode) => {
this.props.navigation.navigate('Search', zipCode);
}
render() {
return (
<View style={styles.container}>
<View style={[styles.boxContainer, styles.boxOne]}>
<Image style={styles.logo} source {require('../../images/Logo.png')} />
<Text style={styles.title}>An application to do things</Text>
<TextInput
style={styles.textInput}
placeholder='Enter a Zip Code'
onChangeText={(zipCode) => this.setState({zipCode})}
>
</TextInput>
</View>
<View style={[styles.boxContainer, styles.boxTwo]}>
<TouchableHighlight onPress={() => this.navigate(this.state.zipCode)}>
<Text style={styles.searchBox}>
Search
</Text>
</TouchableHighlight>
</View>
</View>
);
}
}
Any help/react pointers much appreciated. Thank you!
I think the problem is with home.js since you aren't exporting it. Try this :
export default class Home extends Component { ... }
^^^^^^^^^^^^^^
Add those or just add
export default Home;
at the end of the home.js file
const MyNavigator = createStackNavigator({
RouteNameOne: {
screen: () => <HomeScreen/>
},
RouteNameTwo: {
screen: () => <NewScreen/>
}
}, {
initialRouteName: 'RouteNameOne'
});
It will work.
For anyone else coming here, you could be receiving the "The component for route must be a a React Component" error because you don't have a default export, which was the case for me.
export HomeScreen extends React.Component {
...
vs
export default HomeScreen extends React.Component {
...
Hope this helps someone!
In my case putting this code block inside home.js solved the issue
static navigationOptions = {
navigationOptions: {
title: "scren title",
}
};
I'm new to React Native and trying to set up a navigation between two screens (or pages) using react-navigation package. I'm using a StackNavigator right now.
The problem I am facing is that there seems to be no way to navigate back to a previous screen. All I can do is call navigate(). If, for example, I want to navigate from Home to FRW and back to Home, it seems this will leave me with two instances of Home on the stack that are executed in parallel (one of which can't be seen). My code is something like this:
app.js
import React, { Component } from 'react';
import { AppRegistry } from 'react-native';
import { StackNavigator } from 'react-navigation';
import HomeScreen from './views/HomeScreen.js'
import FRWScreen from './views/FRWScreen.js'
const MainNavigator = StackNavigator({
FRW: { screen: FRWScreen },
Home: { screen: HomeScreen },
}, {
headerMode: 'screen',
headerVisible: false,
navigationOptions: {
header: null
},
initialRouteName: "Home"
});
export default class TestApp extends Component {
render() {
return (
<MainNavigator></MainNavigator>
);
}
}
AppRegistry.registerComponent('TestApp', () => TestApp);
HomeScreen.js
export default class HomeScreen extends Component {
static navigationOptions = {
title: 'Welcome'
};
(...)
onSomeButtonPressed() {
this.props.navigation.navigate('FRW');
}
componentDidMount() {
if (this.locationWatchID !== undefined) return;
this.locationWatchID = navigator.geolocation.watchPosition((position) => {
console.log(this.locationWatchID);
});
}
componentWillUnmount() {
navigator.geolocation.clearWatch(this.locationWatchID);
}
render() {
(...)
return (
<View style={styles.container}>
<MapView ref={ref => { this.map = ref; }} />
<TouchableHighlight
style={styles.someButton}
onPress={this.onSomeButtonPressed.bind(this)}
>
<Text>Press Me</Text>
</TouchableHighlight>
</View>
)
}
}
FRWScreen.js looks similar to HomeScreen.js (and contains .navigate("Home"))
The result of this code is, after navigating to FRW and back, that the geolocation callback is executed twice with different watchIDs. Which makes me believe the HomeScreen is actually on the navigation stack twice.
On your FRWScreen you should use this.props.navigation.goBack(null) instead. See https://reactnavigation.org/docs/navigators/navigation-prop#goBack-Close-the-active-screen-and-move-back.