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!
Related
I am having a problem updating a state. I fetch a JSON result using an address set as 'newurl' and display contents in a FlatList. It all works good and I have a button that when clicked updates the 'newurl' state and reloads the data. The problem is it needs clicking twice to update the state? I have tried putting the setState in a different class but does not work Also I have can not make new 'newurl' to include the state of 'newuser'
The onclick to update the state and reload the data which updates after 2 clicks is :
onPress = () => {
this.setState({
newurl: 'https://www.newtesturl'
}),
fetch(this.state.newurl)
.then(response => response.json())
.then((response)=> {
this.setState({loading: false,list: response.data})
})
}
The problem with making the 'newurl' string when updating it, I cannot seem to add another data state into it. If I use the code below, the 'newurl' is pretty much as is it, without adding the 'newuser' state?
this.setState({
loading: true,
newurl: 'https://www.example.com/userid={this.state.newuser}'
});
Its really annoying because when I add that into a direct url like below it works?
<Image source={{uri: `https://www.example.com/userid={this.state.newuser}`}} />
This is how I am setting up my props in case it makes a differnce? :
type Props = {};
export default class App extends Component<Props> {
constructor(props) {
super(props);
this.state = { list: [], loading: false, newuser: '10101', newurl: '' };
}
Sorry if I have not explained it that well but if you need anything clarifying please ask, thanks
State Updates May Be Asynchronous. See here for more info.
Second issue is because you don't use template string there. See here for more info.
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 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
How can I pass new state to a React Navigation function?
My code currently looks like this:
Simplified view of my parent class:
constructor(props){
super(props)
this.state = {
code: "aaa"
}
this.refresh = this.refresh.bind(this)
}
refresh() {
this.setState({
code: "bbb"
})
}
async componentDidMount(){
const {navigate} = this.props.navigation
navigate("Child", {screen: "Screen Two", code: this.state.code, refresh: this.refresh})
}
In the child class I then do the following:
this.props.navigation.state.params.refresh()
The issue I am facing:
Option 1: If I have the code as it currently is, it will not pass the new state value to the navigator because it is not in the render function
Option 2: If I place the code in the render function, it gives me the warning: "Cannot update during an existing state transition".
What am I doing wrong and how can I fix this?
Further details
I am using this main screen to load some of the details from an API on the web and store them in state. I want to be able to pass a refresh function to the second screen that I will be able to use to reload data from the API onto the main screen. Once the data is loaded back into the state on the main screen it should propagate back down to the second screen. This seems easy to do without using a navigator, but I am not sure how to do it with a navigator.
I am not currently wanting to use redux due to the learning curve, but would like to look into it some time in the future.
So you are trying to call refresh() method inside your child component. If you use this inside render function the refresh() method will be called repeatedly and it will give a warning: "Cannot update during an existing state transition".
If you keep the code as it is, it will update the parent class state. But that update will not be reflected when you accessing this.props.navigation.state.params.code. This will only give the value 'aaa'.
Option 1;
You can use redux and easily handle this scenario.
Option 2;
If you really want to know the value of the parent class state you can pass a function as navigation params to child which will return the value of the state.
Parent class.
constructor(props){
super(props)
this.state = {
code: "aaa"
}
this.refresh = this.refresh.bind(this);
this.getState = this.getState.bind(this)
}
refresh() {
this.setState({ code: "bbb" })
}
getState() {
return this.state.code;
}
async componentDidMount(){
const {navigate} = this.props.navigation
navigate("Child", {screen: "Screen Two", code: this.state.code, refresh: this.refresh, getState: this.getState })
}
Inside your child class use the following code to get the parent class state.
let parentClassState = this.props.navigation.state.params.getState();
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'