Screen Refresh Issue in React Native - react-native

I am making an app that uses firestore. Whenever a data changes in firestore my screen shows two set of data i.e. one set with data before change and other after the change.
for example if I have two data a,b and b is updated to c then the screen shows a,b a,c
After toggling between two screen, the screen gets refreshed and things become okay.
How to get the screen show only new set of data
Any help?
import React,{Component} from 'react';
import { View,ScrollView,AsyncStorage, StyleSheet, Dimensions, SafeAreaView,Platform,StatusBar,Text,Alert,Button,FlatList,TouchableOpacity} from 'react-native';
import {db} from "./FirebaseCon.js";
import firebase from "firebase";
import {useNavigation} from "#react-navigation/native";
import "firebase/firestore";
export default class NoticeList extends React.Component
{
constructor(props)
{
super(props);
this.state={Notice:[],myuser:"",setFlag:null};
}
componentDidMount()
{
let notice=[];
var d=new Date();
var pbm="";
const notref=db.collection("Notice");
async:notref.where("expiry",">=",new Date()).orderBy("expiry","desc").onSnapshot(querySnapshot=>
{
querySnapshot.forEach(doc=>
{
this.setState({Notice:notice});
notice.push(doc.data());
this.setState({Notice:notice});
}
)
});
}
render()
{
const getuser=async()=>
{
let uname=await AsyncStorage.getItem("username");
this.setState({myuser:uname});
this.setState({setFlag:true});
}
if(this.state.setFlag==null)
{
getuser();
}
let myusername=this.state.myuser;
return(
<ScrollView style={nstyles.scene} >
{
this.state.Notice.map(data=>{
const n1=data.expiry.toMillis()-86400000;
const o1=new Date();
const p1=new Date(n1);
let time=p1.getDate();
if(p1.getDate()==o1.getDate())
{
time="Today, ";
}
else
{
time="Yesterday, ";
}
time+=""+p1.getHours();
time+=":"+p1.getMinutes();
const formtmsg=data.message+"\n\n Published By : \n"+data.pby+"\n\n In Order Of :\n"+data.oby
if(data.pby!=myusername)
{
return(
<TouchableOpacity style={nstyles.notlist} onPress={()=>Alert.alert("Notice",formtmsg)}>
<Text style={nstyles.publisher}>{data.oby}<Text style={nstyles.ptime}>{"\n"+time}</Text></Text>
</TouchableOpacity>);
}
})
}
<Text style={{color:"grey",fontSize:16,alignSelf:"center"}}>Published By You</Text>
{
this.state.Notice.map(data=>{
const n=data.expiry.toMillis()-86400000;
const o=new Date();
const p=new Date(n);
let time=p.getDate();
if(p.getDate()==o.getDate())
{
time="Today, ";
}
else
{
time="Yesterday, ";
}
time+=""+p.getHours();
time+=":"+p.getMinutes();
const formtmsg=data.message+"\n\n Published By : \n"+data.pby+"\n\n In Order Of :\n"+data.oby
if(data.pby==myusername)
{
return(
<TouchableOpacity style={nstyles.notlist2} onPress={()=>Alert.alert("Notice",formtmsg)}>
<Text style={nstyles.publisher}>{data.oby}<Text style={nstyles.ptime}>{"\n"+time}</Text></Text>
</TouchableOpacity>);
}
})
}
</ScrollView>);
}
}

Divyansh Dixit, when dealing with lists React uses the key prop of the component in order to re-render the screen.
Your issue can be fixed by adding a unique key prop to the component returned by your map function, in your case the <TouchableOpacity>
It would look something like this
<TouchableOpacity key={data.id}>
...
</TouchableOpacity>
You can read more about this on https://reactjs.org/docs/lists-and-keys.html#gatsby-focus-wrapper

Related

React Native Async Storage - Cant render value on screen

Hey struggling with this one for a day now.
I am trying to store game data just the gameId and the Level for example Game 1 Level 12
Here is my screen
import React, { Component } from 'react';
import AsyncStorage from '#react-native-async-storage/async-storage';
import { Text, StyleSheet, Button, View, ImageBackground, Pressable } from 'react- native';
import bg from "../assets/images/1.jpg";
import styles from '../assets/style';
import { NavigationContainer } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
const setScore = async (gameId, level) => {
//// SETS THE SCORE
try {
await AsyncStorage.setItem(scoreKey, level);
console.log(value)
} catch (error) {
console.log(error)
}
};
const getScore = async (gameId) => {
try {
let value = await AsyncStorage.getItem(JSON.stringify(gameId))
if(value !== null) {
// value previously stored
return JSON.stringify(value)
} else {
return "not started"
}
} catch(e) {
// error reading value
}
};
/// This would add game 1 and level 12
setScore('1','12') /// This part works
const theLevel = getScore(1)
export default function Home({navigation, route}) {
return (
<ImageBackground source={bg} resizeMode="cover" style={styles.imageBG}>
<View style={styles.GameList}>
<Text style={styles.mainTitle}>Current Level is {theLevel}</Text>
</View>
</ImageBackground>
);
}
At the bottom of the above code I want to display the level but I get the error
Error: Objects are not valid as a React child (found: object with keys {_U, _V, _W, _X}). If you meant to render a collection of children, use an array instead.
However If I alert(theLevel) it works fine can someone tell me what I am doing wrong please
Call getScore function from within useEffect hook of your Home component.
export default function Home({ navigation, route }) {
const [level, setLevel] = useState(0);
useEffect(() => {
async function getMyLevel() {
const lvl = await getScore(1);
setLevel(lvl);
}
getMyLevel();
}, []);
const onPress = async () => {
await setScore('1','12');
};
return (
<ImageBackground source={bg} resizeMode="cover" style={styles.imageBG}>
<View style={styles.GameList}>
<Text style={styles.mainTitle}>Current Level is {level}</Text>
</View>
<Button title="Set Score" onPress={onPress} />
</ImageBackground>
);
}

React Native component not getting updated when firebase realtime database listener returns a new value

I am new to react and I am building my final year project using it. I am currently trying to display the game times, however when I add a new game or update a game time the component is not getting re-rendered without me manually refreshing the page.
My code is below:
import React from "react";
import { Button, StyleSheet, Text, TextInput, View, ScrollView } from "react-native";
import * as firebase from 'firebase';
class AdvancedSearchScreen extends React.Component {
constructor(props) {
super(props);
this.state = {
games: []
}
this.props.watchUserData();
var database = firebase.database();
var commentsRef = firebase.database().ref('games');
commentsRef.on('child_added', (data) => {
this.state.games.push(data.val());
});
commentsRef.on('child_changed', (data) => {
this.state.games.push(data.val());
});
}
render() {
return(
<ScrollView>
<View style={styles.container}>
{this.state.games.map(type => <Text>{type.gameTime}</Text>)}
</View>
</ScrollView>
);
}
}
export default AdvancedSearchScreen;
In this case I think you should move you state update to a componentDidMount() using the this.state.games.push(data.val()); will not trigger a new render, you need to use the setState() function, something like this should work for you:
class AdvancedSearchScreen extends React.Component {
constructor(props) {
super(props);
this.state = {
games: []
}
}
componentDidMount() {
this.props.watchUserData();
var database = firebase.database();
var commentsRef = firebase.database().ref('games');
commentsRef.on('child_added', (data) => {
const newItem = data.val()
this.setState({games: [...this.state.games, newItem]})
});
commentsRef.on('child_changed', (data) => {
const newItem = data.val()
this.setState({games: [...this.state.games, newItem]})
});
}
render() {
return(
<ScrollView>
<View style={styles.container}>
{this.state.games.map(type => <Text>{type.gameTime}</Text>)}
</View>
</ScrollView>
);
}
}
export default AdvancedSearchScreen;
something like this should force the render.

Unable to load provider from react-redux module in react native

I am creating a slide bar, In that, I have used the react-redux library. When I call the class which contains the redux-code, it works fine. I want to show this slide bar after login. Therefore, with conditions (I set a state variable if user login successfully then only this page should get rendered), I tried to call the same file which shows a blank page. I printed the console log. I am able to print all the logs. But with conditions, I am not able to load the data.
I don't know much about react-redux.Can you assist me to resolve this?
My code is,
main.js,
import React, {Component} from 'react';
import {
StyleSheet,
Dimensions,
Platform,
View,
StatusBar,
DrawerLayoutAndroid,
} from 'react-native';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import reducer from '../Redux/reducers';
import { setNavigator, setActiveRoute } from "../Redux/actions";
import DrawerContent from '../Navigation/DrawerContent';
import Toolbar from '../Navigation/Toolbar';
import AppNavigation from '../Navigation/AppNavigation';
import { bgStatusBar, bgDrawer } from '../global.styles';
let store = createStore(reducer);
/* getDrawerWidth Default drawer width is screen width - header width
* https://material.io/guidelines/patterns/navigation-drawer.html
*/
const getDrawerWidth = () => Dimensions.get('window').width - (Platform.OS === 'android' ? 56 : 64);
export default class Main extends Component {
constructor() {
super();
this.drawer = React.createRef();
this.navigator = React.createRef();
}
componentDidMount() {
store.dispatch(setNavigator(this.navigator.current));
}
openDrawer = () => {
this.drawer.current.openDrawer();
};
closeDrawer = () => {
this.drawer.current.closeDrawer();
};
getActiveRouteName = navigationState => {
if (!navigationState) {
return null;
}
const route = navigationState.routes[navigationState.index];
// dive into nested navigators
if (route.routes) {
return getActiveRouteName(route);
}
return route.routeName;
};
render() {
return (
<Provider store={store}>
<DrawerLayoutAndroid
drawerWidth={getDrawerWidth()}
drawerPosition={DrawerLayoutAndroid.positions.Left}
renderNavigationView={
() => <DrawerContent closeDrawer={this.closeDrawer} />
}
ref={this.drawer}
>
<View style={styles.container}>
<StatusBar
translucent
animated
/>
<Toolbar showMenu={this.openDrawer} />
<AppNavigation
onNavigationStateChange={(prevState, currentState) => {
const currentScreen = this.getActiveRouteName(currentState);
store.dispatch(setActiveRoute(currentScreen));
}}
ref={this.navigator}
/>
</View>
</DrawerLayoutAndroid>
</Provider>
);
}
}
Login.js
import Main from './main';
render() {
return (
<View>
{this.state.isLoggedIn ?
<Main/>
:
<ChangePassword isUpdatePassword={this.state.isUpdatePassword} callLogin={this.callLogin}/>
);
}
}
If I just call Main class inside render method it works. But It does not work with conditions.

How do I go back in webview? I am using the react-navigation package in react-native

I installed the react-navigation package in react-native
I have implemented tab navigation and one of them is implemented in webview format.
My problem is that if I press the back physical button on Android, I go from the app itself to the previous tab, not back from the webview.
I've already applied the back button for the webview on the internet, but I have not done that.
I tried to display the onNavigationStateChange log when debugging, but it was not updated when url was moved after it was loaded at first startup. Here is the code I implemented:
import React from "react";
import {BackHandler} from "react-native";
import {WebView} from "react-native-webview";
class SermonScreen extends React.Component {
constructor(props) {
super(props);
}
static navigationOptions = {
header: null
};
componentDidMount() {
BackHandler.addEventListener('hardwareBackPress', this.handleBackButton);
}
componentWillUnmount() {
BackHandler.removeEventListener('hardwareBackPress', this.handleBackButton);
}
_onNavigationStateChange(navState) {
console.log(navState);
this.setState({
canGoBack: navState.canGoBack
});
}
handleBackButton = () => {
console.log(this.state);
if (this.state.canGoBack === true) {
this.webView.goBack();
return true;
} else {
return false;
}
};
render() {
return (
<WebView
source={{uri: 'https://m.youtube.com/channel/UCw3kP3qCCF7ZpLUNzm_Q9Xw/videos' }}
ref={(webView) => this.webView = webView}
onNavigationStateChange={this._onNavigationStateChange.bind(this)}
/>
);
}
}
export default SermonScreen;
Following the official webview documnentation you could try to do this: https://github.com/react-native-community/react-native-webview/blob/master/docs/Guide.md#intercepting-hash-url-changes
In general you were almost there, however the way the YT navigation works made it impossible to be caught via the onNavigationStateChange, that's why we inject a JS code that intercepts these hash changes and posts a message to the parent component, we then catch it inside the onMessage handler and set the state variable properly. Copying the injectedJavaScript and onMessage properties to your example should solve your problem.
I prepared a component for you that seems to do what is needed:
* Sample React Native App
* https://github.com/facebook/react-native
*
* #format
* #flow
*/
import React, { Fragment } from "react";
import {
SafeAreaView,
StyleSheet,
ScrollView,
View,
Text,
BackHandler,
StatusBar
} from "react-native";
import { WebView } from "react-native-webview";
import {
Header,
LearnMoreLinks,
Colors,
DebugInstructions,
ReloadInstructions
} from "react-native/Libraries/NewAppScreen";
class App extends React.Component {
constructor(props) {
super(props);
this.startingUrl =
"https://m.youtube.com/channel/UCw3kP3qCCF7ZpLUNzm_Q9Xw/videos";
this.handleBackButton = this.handleBackButton.bind(this);
}
componentDidMount() {
BackHandler.addEventListener("hardwareBackPress", this.handleBackButton);
}
componentWillUnmount() {
BackHandler.removeEventListener("hardwareBackPress", this.handleBackButton);
}
handleBackButton = () => {
console.log(this.state);
const { canGoBack } = this.state;
if (canGoBack) {
this.webView.goBack();
return true;
} else {
return false;
}
};
render() {
return (
<Fragment>
<WebView
source={{ uri: this.startingUrl }}
style={{ marginTop: 20 }}
ref={webView => (this.webView = webView)}
injectedJavaScript={`
(function() {
function wrap(fn) {
return function wrapper() {
var res = fn.apply(this, arguments);
window.ReactNativeWebView.postMessage('navigationStateChange');
return res;
}
}
history.pushState = wrap(history.pushState);
history.replaceState = wrap(history.replaceState);
window.addEventListener('popstate', function() {
window.ReactNativeWebView.postMessage('navigationStateChange');
});
})();
true;
`}
onMessage={({ nativeEvent: state }) => {
if (state.data === "navigationStateChange") {
// Navigation state updated, can check state.canGoBack, etc.
this.setState({
canGoBack: state.canGoBack
});
}
}}
/>
</Fragment>
);
}
}
export default App;
The response above was perfect. I set the state true for canGoBack though; I was getting a null error, so:
constructor(props) {
super(props);
this.startingUrl = "https://app.vethorcardpag.com.br/GIF/login/0/";
this.state = {
canGoBack : true
}
this.handleBackButton = this.handleBackButton.bind(this);
}
Here is a simple solution using the magic of React's State.
Hope this helps.
import React, { useRef, useState } from 'react'
export default function Component () {
// This is used to save the reference of your webview, so you can control it
const webViewRef = useRef(null);
// This state saves whether your WebView can go back
const [webViewcanGoBack, setWebViewcanGoBack] = useState(false);
const goBack = () => {
// Getting the webview reference
const webView = webViewRef.current
if (webViewcanGoBack)
// Do stuff here if your webview can go back
else
// Do stuff here if your webview can't go back
}
return (
<WebView
source={{ uri: `Your URL` }}
ref={webViewRef}
javaScriptEnabled={true}
onLoadProgress={({ nativeEvent }) => {
// This function is called everytime your web view loads a page
// and here we change the state of can go back
setWebViewcanGoBack(nativeEvent.canGoBack)
}}
/>
)
}
Original answer
https://stackoverflow.com/a/74500469/7823800

Odd behavior in passed params with React Navigation

Here's the screen:
import React from 'react';
import { View, StyleSheet, Image, TouchableOpacity, Text } from 'react-native';
import { NavigationActions } from 'react-navigation'
export default class ContentScreen extends React.Component {
render() {
const { state } = this.props.navigation;
const { code } = state.params.code
return(
<Text>CONTENT GOES HERE: {JSON.stringify(state)} / '{code}'</Text>
)
}
}
And the navigation invocation:
<TouchableOpacity key={k} onPress={() => {navigate("Content", {code: k})}} style={styles.listing}>
And here's what's displayed:
CONTENT GOES HERE: {"params":{"code":"222"},"key":"id-151010...","routeName":"Content"} / ''
Which is really #(#$ weird. The value clearly exists at state.params.code, as can be seen by the JSON string, and yet when accessed, it isn't present. Object.values(state.params) and Object.keys(state.params) give the expected results (["code"] and ["222"]).
So, like, any ideas as to what's going on? Or ideas about how to further investigate?
Looks like you are destructuring one level too deep.
const { code } = state.params.code;
This is basically looking for this.props.state.params.code.code
Try:
Const { code } = state.params;
Or:
const { navigation: { state: { params: { code } } } } = this.props;