I am trying to get navigation between views to work in React Native like so:
newUserFlow() {
this.props.navigator.push({
title: "Create Account",
component: F8InfoView,
});
}
<TopFiveButton
style={styles.button}
onPress={this.newUserFlow.bind(this)}
caption="Create Account"
/>
However, I am running into this error on click:
ExceptionsManager.js:75 Argument 0 (NSNumber) of RCTNavigatorManager.requestSchedulingJavaScriptNavigation must not be null
This worked previously, but something seems to have changed. What could be the issue?
The debugger references this in the exceptions manageR:
// Flow doesn't like it when you set arbitrary values on a global object
(console: any)._errorOriginal = console.error.bind(console);
am I breaking something in flow?
Edit
index.ios.js looks like this:
class nomad extends Component {
render() {
return (
<NavigatorIOS
style={styles.container}
initialRoute={{
title: 'Nomad',
component: LoginScreen
}} />
);
}
};
then in LoginScreen:
class LoginScreen extends React.Component {
newUserFlow() {
this.props.navigator.push({
title: "Create Account",
component: F8InfoView,
// passProps: {userInfo: this.props.userInfo}
})
}
render() {
return (
<F8Button
style={styles.button}
onPress={this.newUserFlow.bind(this)}
caption="Create Account"
/>
);
}
}
Clicking the button gives me the error.
Which version are you using for react ? Are you sure to be on the last stable version ? I ask you that because in ExceptionsManager.js, I cannot see "NSNumber" on line 75...
Let me underline that this question is very similar... so have you tried to put
navigator.push(routes[1]);
inside the onPress prop. ?
something like:
<F8Button onPress={
() => { if (route.index === 0){
navigator.push(routes[1]);
} else {
navigator.pop();
}
}
...
...
To conclude you may also have a closer look at https://facebook.github.io/react-native/docs/navigator.html
Regards
Hope this solution helps you a bit
Problems which might have occured
The problem was that once you execute the onPressevent, the i index is beyond bounds so the node can't be found.
Something tells me this happened because you were trying to access a ref that is null because you put the dropdown list in the renderScene function of a navigator. I had the exact same problem.
Please consider posting your code or debugging the findNodeHandle result, because this error is produced once you send null as a node.
The error may br due to React being unable to find the node because findNodeHandle doesn't get a React node as a parameter.
It may be the problem that you are sending a null node to the event. Try to debug what you're getting in the findNodeHandle function.
Solutions
You have to change all the refs to this.refs.navigator.refs['any reference made in your code'] etc.
Don't forget to give the actual navigator a ref
<Navigator
ref='navigator'
Related
The following is a first attempt at learning to simply change the style of an element onPress in react native. Being well versed in web languages I am finding it difficult as it is not as straight forward.
For reasons as yet unknown, the element requires two clicks in order to execute.
export class NavTabItem extends React.Component {
constructor(props) {
super(props);
this.state = {
active: false
}
this.NavTabAction = this.NavTabAction.bind(this)
}
NavTabAction = (elem) => {
elem.setState({active: !elem.state.active})
}
render() {
return (
<TouchableOpacity
style={this.state.active ? styles.NavTabItemSelected : styles.NavTabItem}
onPress={()=> {
this.NavTabAction(this)
}}>
<View style={styles.NavTabIcon} />
<Text style={styles.NavTabLabel}>{this.props.children}</Text>
</TouchableOpacity>
);
}
}
Other issues:
I also have not worked out how a means of setting the active state to false for other elements under the parent on click.
Additionally, Is there a simple way to affect the style of child elements like with the web. At the moment I cannot see a means of a parent style affecting a child element through selectors like you can with CSS
eg. a stylesheet that read NavTabItemSelected Text :{ // active style for <Text> }
Instead of calling elem.setState or elem.state, it should be this.setState and elem.state.
NavTabAction = (elem) => {
this.setState(prev => ({...prev, active: !prev.active}))
}
And instead of passing this in the onPress, you should just pass the function's reference.
onPress={this.NavTabAction}>
You should also remove this line because you are using arrow function
// no need to bind when using arrow functions
this.NavTabAction = this.NavTabAction.bind(this)
Additionally, Is there a simple way to affect the style of child elements like with the web
You could check styled-component, but I think that feature don't exists yet for react native. What you should do is pass props down to child components.
Thanks to everyone for their help with this and sorting out some other bits and pieces with the code.
The issue in question however was that the style was changing on the second click. A few hours later and I have a cause and a solution for anyone suffering from this. Should any of the far more experienced people who have answered this question believe this answer is incorrect or they have a better one, please post it but for now here is the only way I have found to fix it.
The cause:
Using setState was correctly re rendering the variables. This could both be seen in the console via console.log() and directly outputted in the render making them visible.
However, no matter what was tried, this did not update the style. Whether it was a style name from the Stylesheet or inline styles, they would update on the second click rather than the first but still to the parameters of the first. So if the first click should make a button turn from red to green, it would not do so even though the new state had rendered. However if a subsequent click should have turned the button back to red then the button would now go green (like it should have for the first click). It would then go red on the third click seemingly always one step behind the status passed to it.
Solution
To fix this, take the style off the the primary element (forgive terminology, someone edit), in my case, the TouchableOpacity element. Add in a child View element and place the styles on that View element instead along with the ternary operator and wallah.
It seems any change to status on the effective master element or container if you prefer, only takes affect after another render, not that contained in setStatus.
Final code:
export class NavTabItem extends React.Component {
constructor(props) {
super(props);
this.state = {
active: false
}
}
NavTabAction = () => {
this.setState({active: !this.state.active})
}
render() {
this.state.active == true ? console.log("selected") : console.log("unselected")
return (
<TouchableOpacity onPress={this.NavTabAction}>
// added View containing style and ternary operator
<View style={this.state.active == true ? styles.NavTabItemSelected : styles.NavTabItem}>
<View style={styles.NavTabIcon} />
<TextCap11 style={styles.NavTabLabel}>{this.props.children}</TextCap11>
</View>
// End added view
</TouchableOpacity>
);
}
}
I'm trying to pass a few params between a Tab Navigator. Below is the structure of my program. In bold are the routes
App(Tab Navigator): { Main(stack) & Filter(screen) }
Main(Stack Navigator): { Home(screen) & MediaDetails(screen) }
I have a button on the screen associated with Filter which has an onPress() function. I'm passing a few params(but let's only consider the param to for the sake of this question).
this.props.navigation.navigate('Home', {
to: this.state.to,
}
Now in the screen associated with Home, I'm reading the param inside the state like this:
state = {
to: this.props.route.params.to
}
Inside App.js, I've set the initial value of to to be '2020' like this:
<Stack.Screen
name="Home"
component={HomeScreen}
initialParams={{
to: '2020'
}}
/>
The initial value is indeed set to 2020. I press the button. Let's say I'm setting to to 1900. Just before this.props.navigation.navigate executes, I console.log(this.state.to) the value and it is indeed updated to 1900. However, as the screen changes to Home, the value reverts back to 2020(observed via console.log)
Could someone point out the cause for this spooky behavior? I've been trying to debug this for many hours with no luck. React Navigation 5 is pretty new as well so couldn't find anything similar online. Any help will be greatly appreciated. Thank you for reading all the way.
Edit: Issue has been resolved and full code has been removed!
/* 1. Navigate to the Details route with params */
onPress={() => {
navigation.navigate('Details', {
itemId: 86,
otherParam: 'anything you want here',
});
}}
/* 2. Get the param */
const DetailsScreen = ({ route, navigation }) => {
const { itemId } = route.params;
const { otherParam } = route.params;
return (
<View></View>
);
};
for more info: https://reactnavigation.org/docs/params/
I solved this problem by updating the state in shouldComponentUpdate().
This bug was due to the state not being updated as the screen remained mounted.
This was possible because of ravirajn22 on GitHub who explained to me the reasoning behind this bug and a possible solution.
Link to the answer: https://github.com/react-navigation/react-navigation/issues/6863
As it is stated Here, you can do it like this:
navigation.navigate('otherStackName', {
screen: 'destinationScreenName',
params: { user: 'jane' },
});
In Functional Component write simple logic like this:-
In the parent component
<TouchableOpacity onPress={()=>navigation.navigate('childRoute', {data_Pass_to_Child: "Hello I am going to child component"})}>
In the child component
const Chat = ({route}) => {
console.log(route.params.data_Pass_to_Child);
return(whatever based on your UI)
}
Note:
Replace childRoute with your route name and also replace data {data_Pass_to_Child: "Hello I am going to child component"} with whatever data you want to pass.
send
props.navigation.navigate('YourScreenName', {
your_key: any_value
});
receive in YourScreenName.js
var valueReceived = props.navigation.state.params.your_key;
I am building an app where the users are prompted to choose their language the first time they launch the app. The language is then stored locally using AsyncStorage.
Every time the app launches the language is retrieved from storage and saved into the global.lang variable to be used by all components:
AsyncStorage.getItem('settings', (err, item) => {
global.lang = item.lang;
});
When I use the global.lang variable in the render() method in any component everything seems to be ok. However I run into trouble when trying to use the same variable when initializing my navigators:
const TabNavigator = createBottomTabNavigator(
{
Home: {
screen: HomeScreenNavigator,
navigationOptions:{
title: strings['en'].linkHome, --> this works
}
},
News: {
screen: NewsScreen,
navigationOptions:{
title: strings[global.lang].linkNews, --> this fails
}
}
});
I believe that this because the value is not retrieved from AsyncStorage by the time that the navigators are constructed. If I set the global.lang manually (eg. global.lang = 'en';) it seems to be OK, but not when I try to retrieve it from the storage.
Is there something that I am missing? Could I initialize the navigator with a default language and change the title later based on the value retrived?
Any help would be greatly appreciated.
The navigators are constructed in the app launch. So you would need to use some placeholder text and use the method described here where you change all screen titles based on the screen key...
Or... this sounds insane and i have never tried it. But you can use a loading screen where you retrieve the languaje settings. then... via conditional rendering you "render" a navigator component . Idk if it would work the same way , but you can try it. below some code that i just created for this purpose
export default class MainComponent extends React.Component {
constructor(props) {
super(props);
this.state = { hasLanguage:false};}
componentDidMount(){
this.retrieveLanguage()
}
async retrieveLanguage(){
//await AsyncStorage bla bla bla
//then
this.setState({hasLanguage:true})
}
render() {
return (
{
this.state.hasLanguage?
<View>
//this is a view that is rendered as a loading screen
</View>:
<Navigator/>//this will be rendered, and hence, created, when there is a language retrieved
}
);
}
}
Again. I don't know if react navigation creates the navigator at render . If so. When it creates a navigator , there should be the languaje to be used there
I've split out the navigationOptions to the top of each screen to make things easy to understand, rather than keeping it in the TabNavigator. Here's an example of one screen:
static navigationOptions = ({navigation}) => {
const {params = {}} = navigation.state;
return {
title: params.title != undefined ? `${params.title}` : "Step 1: Select a Letter",
tabBarOnPress: ({...props, scene})=>{
params.onFocus();
},
titleStyle: {
textAlign: 'center'
}
}
};
Once a user selects a letter on this screen, I switch the view to show a different component (a list of items that start with that letter, for example).
I'd like to change the title of the TabNavigator Header to now show something like "Step 2: Select an item from the list". I saw on a thread that you can call a method from the method that rebuilds the view like this:
_letterSelectedHandler = (letter) => {
this._changeTitle("Step 2: Select an item...");
...
}
With the method:
_changeTitle = (title) => {
this.props.navigation.setParams({title});
}
Unfortunately, it seems to create an infinite loop. Is there a better way of simply changing the TabNavigator Header Title?
I should mention that I'm not using Redux or anything too complex, since I'm new to React Native. Please let me know if I can provide any more information to help illustrate the issue I'm having.
I am trying to create a button which is displaying and hiding a navigation menu on click. I am using Redux to get the current state into the Component, but something is not working with the onPress function.
When pressing the button I want to check the current state of this.state.showNavigation (can be true/false) but I am getting an "undefined is not an object" error immediately after clicking the button.
I think I am running into a lifecycle issue here. I already tried to ship around this via setting the state in componentWillMount like that:
componentWillUpdate(){
this.state = NavigationStore.getState();
}
Anyway that didn't help. Some advise is much appreciated. Thanks!
Heres my code:
class NavigationButton extends React.Component {
constructor(props, context) {
super(props, context);
this.state = NavigationStore.getState();
NavigationStore.subscribe(() => {
this.setState(NavigationStore.getState());
});
// alert(this.state.showNavigation);
}
render() {
return (
<TouchableOpacity
onPress={this.handlePressButton}
style={navigationButtonStyles.button}>
<Image source={buttonImage} />
</TouchableOpacity>
);
}
handlePressButton() {
if(this.state.showNavigation){
NavigationStore.dispatch({
type: 'HIDE_NAVIGATION',
});
}
else{
NavigationStore.dispatch({
type: 'SHOW_NAVIGATION',
});
}
}
};
I was using a pretty strange approach, I did not use the react-redux package for the whole thing and couldn't connect my store. I deep dived into https://github.com/bartonhammond/snowflake and got it solved, the snowflake example was really helpful to understand the basics!