React-navigation to re-render route when parent state changes - reactjs

I have a page which receives data from a source in specific Intervals and sets them into a state, I show part of that data inside my page, and I have a button which opens another page(via react-navigation) and I want rest of data to be displayed in there, but the problem is, it won't refresh the second page when state of parent changes, below is simplified code:
import React from "react";
import { View, Text, Button } from "react-native";
import { createStackNavigator, createAppContainer, NavigationActions } from "react-navigation";
class HomeScreen extends React.Component {
constructor() {
super();
this.state = { number: 0 };
}
genNumber() {
number = Math.random();
this.setState({ number });
}
componentWillMount() {
setInterval(() => { this.genNumber() }, 1000);
}
render() {
return (
<View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
<Text>Home Screen</Text>
<Button title="Go" onPress={() => this.props.navigation.navigate('Test', { k: () => this.state.number })} />
</View>
);
}
}
class TestScreen extends React.Component {
constructor() {
super()
this.state = { t: 1 };
}
render() {
return (
<View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
<Text>Test {this.props.navigation.getParam('k')()}</Text>
</View>
);
}
}
const AppNavigator = createStackNavigator({
Home: {
screen: HomeScreen,
},
Test: {
screen: TestScreen
}
});
export default createAppContainer(AppNavigator);

you are using navigation params to pass data from parent class to child class and you want a reflection of state change in child class but it won't work in it.
For that use Component and pass data as props you immediate reflection of data.

React will not re-render a screen if a data passed to it through parameters changed, unfortunately this is how react navigation works.
To solve it, you can change the way your are passing data between screens.
instead of using navigation parameters use a global state(example redux).
in this case set the number variable to a state in redux,
then select that number in your Test screen and when number changes it will trigger a re-render.

Related

No safe area insets value available. Make sure you are rendering `<SafeAreaProvider>` at the top of your app. - how can i ressolve this error?

export default class CalorieScreen extends Component {
constructor() {
super();
this.state = { text: "" };
}
render() {
const { calories } = this.state;
return (
<View style={styles.container}>
<Header
backgroundColor={"#9c8210"}
centerComponent={{
text: "Monkey Chunky",
style: { padding: 100, color: "#fff", fontSize: 20 },
}}
/>
</View>
);
}
}
I created a login screen that goes into my calorie screen when i click the buttun it takes me to the screen but this error appears
Make sure you are rendering <SafeAreaProvider> at the top of your app. Something like this.
import { SafeAreaProvider } from 'react-native-safe-area-context';
...
return <SafeAreaProvider>...</SafeAreaProvider>;
...
wrapp all the component in SafeAreaProvide in app.js
import { SafeAreaProvider } from 'react-native-safe-area-context';
...
return ...;
As you are using react-native-elements make sure you install version 2.2.1

Using dropdown component in react-navigation throws error when handler is called

I'm trying to use react-navigation with react-native-dropdown in order to have a dropdown in the header to change what is rendered in the view below.
Whenever I try to execute the handler, react-native-dropdown I get the error "undefined is not a function (near'...this.props.data.map')when react-native-dropdown attempts to render again and maps the dropdown using (this.props.data.map((rows, index) => etc.
Below is the code I'm using:
import React, {Component} from "react"
import {Dimensions, ScrollView, StyleSheet, width, View, TouchableOpacity} from "react-native"
import {Images} from "../../theme"
import DropdownMenu from 'react-native-dropdown-menu';
import Pdf from 'react-native-pdf';
import { Header } from "react-native/Libraries/NewAppScreen";
import { connect } from 'react-redux';
import { NavigationActions } from 'react-navigation';
import { drop } from "lodash";
class InformationScreen extends Component {
constructor(props) {
super(props)
var informationScreens = props.global.appConfig.information_screens
var informationScreenTitles = Object.keys(informationScreens)
state = {
informationScreens: informationScreens,
selectedDropdown: informationScreens[0]
}
}
static navigationOptions = ({ navigation }) => {
return {
headerTitle: <DropdownMenu
style={{flex: 1}}
bgColor={'clear'}
tintColor={'#666666'}
activityTintColor={'rgba(2, 122, 255, 1.0)'}
// arrowImg={}
// checkImage={}
// optionTextStyle={{color: '#333333'}}
// titleStyle={{color: '#333333'}}
// maxHeight={300}
handler={(selection, row) => navigation.setParams({'headerTitle': navigation.state.params[selection][row]})}
data={navigation.state.params}
/>
};
};
render() {
return (
<View style={styles.container}>
<Pdf
source={{ uri: "https://someurl.com/dl/sites/default/files/page/2020%20BYU%20Football%20Almanac_3.pdf", cache: true}}
onLoadComplete={(numberOfPages,filePath)=>{
console.log(`number of pages: ${numberOfPages}`);
}}
style={styles.pdf}
/>
</View>
)
}
}
const styles = StyleSheet.create({
image: {
flex: 1,
},
container: {
flex: 1,
justifyContent: 'flex-start',
alignItems: 'center',
marginTop: 25,
},
pdf: {
flex:1,
width:Dimensions.get('window').width,
height:Dimensions.get('window').height,
}
})
function mapStateToProps(state) {
const global = state.get('global');
return { global };
}
export default connect(mapStateToProps)(InformationScreen);
Ray you were absolutely correct, I figured it out about 5 minutes before you posted.
navigation.state.params was valid and the dropdown would populate, however when I would try to setParams, it would change the format of params to a JSON object instead of an array.
This was remedied by putting my array one JSON object deeper so the object still contained the array after setParams was called. I then called that object in the data.
data={navigation.state.params.informationScreens}
Thank you so much for your help, I'll start using StackOverflow more often.
I suspect this.props.data is either not provided or not an array, so this.props.data.map is undefined, and attempting to invoke it will get you an 'undefined is not a function' error. Is navigation.state.params an array?
If you pass data={navigation.state.params || []} does the issue go away?

Clear Interval when app is in background state in React Native

I would like to know how to clear an Interval whenever my app goes into Background State.
Sometimes when I click the Home button my application is being closed (willUnmount event is not fired) and then the interval is never cleared.
How did you know the interval is never cleared if app closed?(Just curious) If App is being closed, it should be not working at all, isn't it? Only if you have background timer maybe will run?
But I think it should be stopped if you close the app?
I think in your case, you can use AppState from react-native, to detect if app went back to foreground then reset the interval?
import React, {
Component
} from "react";
import {
AppState,
StyleSheet,
Text,
View
} from "react-native";
export default class AppStateExample extends Component {
state = {
appState: AppState.currentState
};
componentDidMount() {
AppState.addEventListener("change", this._handleAppStateChange);
}
componentWillUnmount() {
AppState.removeEventListener("change", this._handleAppStateChange);
}
_handleAppStateChange = nextAppState => {
if (
this.state.appState.match(/inactive|background/) &&
nextAppState === "active"
) {
console.log("App has come to the foreground!"); //reset interval here
}
this.setState({
appState: nextAppState
});
};
render() {
return (
<View style = {
styles.container
} >
<Text>
Current state is: {
this.state.appState
}</Text>
</View >
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center"
}
});

How to re-render a react-native components when its prop changes?

Sorry if this is a bit of a noob question. I'm trying to set up a react-native multiplayer game with a lobby which shows who your opponents are. The problem is that even when the prop representing the opponents updates, the component does not re-render, even though a console log shows me the prop has changed (and that render() was called?). Some snippets from my code are below:
In Lobby.js:
export default class Lobby extends Component {
render() {
console.log('In Lobby, opponents = ' + this.props.opponents);
return(
<View style={{ flex: 1, justifyContent: "center", alignItems: 'center' }}>
<Text>Welcome to the lobby { this.props.username }!</Text>
<Text>Opponents: { this.props.opponents }</Text>
... more text and buttons ...
</View>
);
}
}
As I said, I can see in the console that this.props.opponents does change but the screen doesn't seem to re-render that <Text> component. I initially thought this may be because this.props.opponents is set to the opponents value of my Main component's state, but the console log seems to debunk this.
I've looked here on SO and found suggestions to add shouldComponentUpdate() and componentDidUpdate() to my component but the nextProps parameter would always actually be the same as this.props. I.e. if I add:
shouldComponentUpdate(nextProps) {
console.log('this.props.opponents=' + this.props.opponents + '; ' + 'nextProps.opponents=' + nextProps.opponents);
return this.props.opponents != nextProps.opponents;
}
This never actually returns True for me, even though the prop is definitely changing. (I also don't know what code I would actually want in componentDidUpdate() either, since I just want it to re-render). It just shows that, yes the prop is different but that both nextProps.opponents and this.props.opponents have changed to that same new value (i.e. 'TestUser').
I'm really at a loss here. Thanks in advance.
EDIT: Added simplified version of my code.
App.js:
import React, { Component } from 'react';
import Main from './components/Main';
export default function App() {
return (
<Main/>
)
}
In Main.js:
import React, { Component } from 'react';
import { View } from 'react-native';
import Lobby from './Lobby';
export default class Main extends Component {
constructor(props) {
super(props);
this.state = {
opponents: [], // array of strings of other users' usernames
in_lobby: true // whether user is in lobby or not
};
this.testClientJoin = this.testClientJoin.bind(this);
}
testClientJoin() {
console.log('Called testClientJoin().');
let currentOpponents = this.state.opponents;
currentOpponents.push('TestUser');
this.setState({
opponents: currentOpponents
});
}
render() {
return(
<View style={{ flex: 1}}>
{
this.state.in_lobby &&
<Lobby
opponents={this.state.opponents}
testClientJoin={this.testClientJoin}
/>
}
</View>
);
}
}
In Lobby.js:
import React, { Component } from 'react';
import { View, Button, Text } from 'react-native';
export default class Lobby extends Component {
render() {
console.log('Opponents prop = ' + this.props.opponents);
return(
<View style={{ flex: 1, justifyContent: "center", alignItems: 'center' }}>
<Text>Welcome to the lobby!</Text>
<Text>Your opponents are {this.props.opponents}.</Text>
<Button
title='Add test opponent'
onPress={this.props.testClientJoin}
/>
</View>
);
}
}
So. When I tap the button to Add test opponent, the console logs
Called testClientJoin().
Opponents prop = TestUser
which shows the prop has indeed updated. However, the doesn't actually reflect this change, until I force some update (i.e. since I'm using expo, I just save the file again, and I finally see the text appear on my phone. I'm sure I'm just being an idiot but would love to know how to get the desired behavior of the text component updating.
Well I found an answer to my question which I'll leave here if anyone finds this. What I had to do was the following:
Main.js:
Instead of passing this.state.opponents directly as a prop to <Lobby>, pass a copy of this array instead.
render() {
<Lobby
testClientJoin={this.testClientJoin}
opponents={ [...this.state.opponents] }
/>
And this has the desired result. So the moral of the story is, don't try to directly pass an array state from a parent to a child component.

Unable to map dynamic json data to react native swiper slider using axios in react native

I want to pass the image URL dynamically to react-native-swiper but it gives me type error of undefined. How can I make it dynamic? I am new to react-native So kindly help me and Thanks in advance.
I am on MacX using react native
import React, {Component} from 'react';
import {
Text,
View,
Image,
Dimensions
} from 'react-native';
import axios from 'axios';
import Swiper from 'react-native-swiper';
import { scale } from '../../assets/css/Scaling';
import {ROOT_URL} from '../../../get_connection';
const {width} = Dimensions.get('window');
const SliderImg = props => (
<View style={styles.container}>
<Image style={styles.image} source={{uri: props.uri}}/>
</View>
);
const styles = {
container: {
flex: 1,
justifyContent: 'center',
paddingTop: scale(15),
},
image: {
flex: 1,
width,
}
};
class Slider extends Component {
constructor(props){
super(props)
this.state = {
imagesSlider: ["http://burgerhut1313.in/KaptureOne/admin/upload/slider_img/slider.png","http://burgerhut1313.in/KaptureOne/admin/upload/slider_img/slider1.png","http://burgerhut1313.in/KaptureOne/admin/upload/slider_img/slider2.png"]
}
};
componentDidMount(){
this.getSliderContent();
}
getSliderContent() {
// We're using axios instead of Fetch
axios
// The API we're requesting data from
.get(`${ROOT_URL}/getSlider.php`)
// Once we get a response, we'll map the API endpoints to our props
.then(response =>
response.data,
)
// Let's make sure to change the loading state to display the data
.then(slides => {
this.setState({
slides: slides
});
})
// We can still use the `.catch()` method since axios is promise-based
.catch(error => console.log(error));
}
render (){
return (
console.log("slides state: "+this.state.slides),
console.log("imagesSlider state: "+this.state.imagesSlider),
<View style={{flex:1}}>
<Swiper
autoplay
autoplayDelay={3}
height={240}
dotColor='#fff'
paginationStyle={{ bottom: scale(10) }}
activeDotColor= '#0071bc'
>
{
this.state.slides.map((item, i) => <SliderImg
uri={item}
key={i}
/>)
}
</Swiper>
</View>
)
}
};
export { Slider };
I expect to fetch the slides URL and then display it but it gives an error of undefined when I map the slides data
This shows in the console:
At first render, your function is trying to do a map() on an undefined value, as you didn't define it in constructor. You either have to check if the values exists as array or initialize it in the constructor as array:
constructor(props){
super(props)
this.state = {
imagesSlider: ["http://burgerhut1313.in/KaptureOne/admin/upload/slider_img/slider.png","http://burgerhut1313.in/KaptureOne/admin/upload/slider_img/slider1.png","http://burgerhut1313.in/KaptureOne/admin/upload/slider_img/slider2.png"],
slides:[]
}
};

Resources