I am trying to make a game in react-native. I want to render 200+ views on the Game screen. Each View has a pressable functionality. Whenever I press the View I need to run a function that will change the View background color and update score on the game context. But Whenever I try to press any View it took some time to change the background and update the context.
Note
I am using the expo as a development environment and I am using a real device too.
My View Component
import { useEffect, useState, memo } from "react";
import { useContext } from "react";
import { gameContext } from "./gameContext";
import { Pressable, View } from "react-native";
function CheckBoxCom() {
const [active, setActive] = useState(false);
const { score, setScore } = useContext(gameContext);
useEffect(() => {
let time = setTimeout(() => {
setActive(false);
}, Math.floor(Math.random() * 35000));
return () => clearTimeout(time);
}, [active]);
const handlePress = () => {
if (active) return;
setActive(true);
setScore(score + 1);
};
return (
<View>
<Pressable onPress={handlePress}>
<View
style={{
width: 20,
height: 20,
borderWidth: 2,
borderColor: active ? "green" : "gray",
margin: 3,
borderRadius: 3,
backgroundColor: active ? "green" : null,
}}
></View>
</Pressable>
</View>
);
}
export default memo(CheckBoxCom);
Game Screen Component
import { useContext, useEffect, useState } from "react";
import { StatusBar } from "expo-status-bar";
import { StyleSheet, Text, View, FlatList } from "react-native";
import CheckBox from "./CheckBox";
import { gameContext } from "./gameContext";
export default function Game({ navigation }) {
const { score, time, setTime, boxList } = useContext(gameContext);
const [intervalId, setIntervalId] = useState("");
useEffect(() => {
const int = setInterval(() => {
setTime((prvTime) => prvTime - 1);
}, 1000);
setIntervalId(int);
return () => clearInterval(int);
}, []);
if (time === 0) {
clearInterval(intervalId);
navigation.navigate("Score", { score });
}
return (
<View style={{ flex: 1 }}>
<StatusBar style="auto" />
<View style={styles.textHeader}>
<Text>Score : {score}</Text>
<Text>Time Left: {time}s</Text>
</View>
<View style={styles.checkBoxContainer}>
<FlatList
style={{ alignSelf: "center" }}
data={boxList}
initialNumToRender={50}
numColumns={12}
renderItem={(i) => <CheckBox />}
keyExtractor={(i) => i.toString()}
scrollEnabled={false}
/>
</View>
</View>
);
}
const styles = StyleSheet.create({
textHeader: {
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
width: "100%",
marginTop: 40,
paddingHorizontal: 30,
},
checkBoxContainer: {
margin: 20,
display: "flex",
flexWrap: "wrap",
height: "80%",
overflow: "hidden",
flexDirection: "row",
},
});
How can I run view function immediately whenever I press it?
The reason it is slow is that when you press on a view, all 200+ CheckBoxCom components rerender. If they don't need to, we can improve performance by trying to prevent those unnecessary rerenders.
I believe the major bottleneck here is the gameContext. It groups together a lot of states and if any of these were to change, all components will rerender. It provides score state that you are reading within each CheckBoxCom. Whenever the score changes all CheckBoxCom components will re-render. If you change handlePress() to:
const handlePress = () => {
if (active) return;
setActive(true);
setScore(score => score + 1);
};
Please note the use of callback to update the score in the above handler. In this case, we don't need to read score from context, so we can remove it from the game context provider, only pass setScore. Removing score from the context provider is important because not doing so will rerender all components using the context even if you don't specifically destructure score.
Also, make sure you don't have a lot of state variables within a single context. Split it into multiple contexts if you have different states in there. In this way, you will be able to reduce unnecessary rerenders of the CheckBoxCom components.
Since your CheckBoxCom components have an internal state, using React.memo() will not help to prevent rerenders because it only works for rerenders resulting from changed props.
But if you are able to refactor them to lift the active state up to the parent i.e. something like activeViews or something (which could be a map of indexes which are true i.e. active), then you can pass the active state as a boolean prop to each CheckBoxCom component. And if we also pass setScore via a prop instead of via context, we can benefit from React.memo(). BTW it is not necessary to wrap setState methods with useCallback().
The end result will be: CheckBoxCom components with zero internal states and no reliance on context, in other words, pure components i.e. components which work nicely with React.memo().
Use pagination in flatlist
for ref: Pagination in flatlist
import React, { Component } from 'react';
import {
View,
Text,
TouchableOpacity,
StyleSheet,
FlatList,
Platform,
ActivityIndicator,
} from 'react-native';
export default class App extends Component {
constructor() {
super();
this.state = {
loading: true,
//Loading state used while loading the data for the first time
serverData: [],
//Data Source for the FlatList
fetching_from_server: false,
//Loading state used while loading more data
};
this.offset = 0;
//Index of the offset to load from web API
}
componentDidMount() {
//fetch('http://aboutreact.com/demo/getpost.php?offset=' + this.offset)
fetch('https://www.doviz.com/api/v1/currencies/all/latest')
.then(response => response.json())
.then(responseJson => {
responseJson = responseJson.slice((this.offset*12),((this.offset+1)*12)-1)
console.log("offset : "+this.offset);
console.log(responseJson.slice((this.offset*12),((this.offset+1)*12)-1));
//Successful response from the API Call
this.offset = this.offset + 1;
//After the response increasing the offset for the next API call.
this.setState({
// serverData: [...this.state.serverData, ...responseJson.results],
serverData: [...this.state.serverData, ...responseJson],
//adding the new data with old one available in Data Source of the List
loading: false,
//updating the loading state to false
});
})
.catch(error => {
console.error(error);
});
}
loadMoreData = () => {
//On click of Load More button We will call the web API again
this.setState({ fetching_from_server: true }, () => {
//fetch('http://aboutreact.com/demo/getpost.php?offset=' + this.offset)
fetch('https://www.doviz.com/api/v1/currencies/all/latest')
.then(response => response.json())
.then(responseJson => {
responseJson = responseJson.slice((this.offset*12),((this.offset+1)*12)-1)
console.log("offset Load : "+this.offset);
console.log(responseJson);
//Successful response from the API Call
this.offset = this.offset + 1;
//After the response increasing the offset for the next API call.
this.setState({
//serverData: [...this.state.serverData, ...responseJson.results],
serverData: [...this.state.serverData, ...responseJson],
fetching_from_server: false,
//updating the loading state to false
});
})
.catch(error => {
console.error(error);
});
});
};
renderFooter() {
return (
//Footer View with Load More button
<View style={styles.footer}>
<TouchableOpacity
activeOpacity={0.9}
onPress={this.loadMoreData}
//On Click of button calling loadMoreData function to load more data
style={styles.loadMoreBtn}>
<Text style={styles.btnText}>Loading</Text>
{this.state.fetching_from_server ? (
<ActivityIndicator color="white" style={{ marginLeft: 8 }} />
) : null}
</TouchableOpacity>
</View>
);
}
render() {
return (
<View style={styles.container}>
{this.state.loading ? (
<ActivityIndicator size="large" />
) : (
<FlatList
style={{ width: '100%' }}
keyExtractor={(item, index) => index}
data={this.state.serverData}
renderItem={({ item, index }) => (
<View style={styles.item}>
<Text style={styles.text}>
{item.currency}
{'.'}
{item.code}
</Text>
</View>
)}
onEndReached={this.loadMoreData}
onEndReachedThreshold ={0.1}
ItemSeparatorComponent={() => <View style={styles.separator} />}
ListFooterComponent={this.renderFooter.bind(this)}
//Adding Load More button as footer component
/>
)}
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
paddingTop: 30,
},
item: {
padding: 10,height:80
},
separator: {
height: 0.5,
backgroundColor: 'rgba(0,0,0,0.4)',
},
text: {
fontSize: 15,
color: 'black',
},
footer: {
padding: 10,
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'row',
},
loadMoreBtn: {
padding: 10,
backgroundColor: '#800000',
borderRadius: 4,
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
},
btnText: {
color: 'white',
fontSize: 15,
textAlign: 'center',
},
});
Related
So I'm having some bundling problem with a single function that is not even invoked. const { status } = await BarCodeScanner.requestPermissionsAsync(); The function should be invoked when the QR code is scanned and the string information gets parsed and saved to a SQLite database. There is something wrong with bundling the app. I tried to set a setTimeOut at the root but to no avail. Here is the error That I'm getting:
ERROR TypeError: undefined is not an object (evaluating '_Splash.default.TABLE_REFERENCE')
LOG Running "main" with {"rootTag":91,"initialProps":{}}
ERROR Invariant Violation: "main" has not been registered. This can happen if:
* Metro (the local dev server) is run from the wrong folder. Check if Metro is running, stop it and restart it in the current project.
* A module failed to load due to an error and `AppRegistry.registerComponent` wasn't called.
Here is the actual screen. nothing fancy really.
import { StackScreenProps } from "#react-navigation/stack";
import { BarCodeScanner, BarCodeScannerResult } from "expo-barcode-scanner";
import AsyncStorage from "#react-native-async-storage/async-storage";
import React, { useEffect, useState } from "react";
import {
Alert,
StyleSheet,
Text,
useWindowDimensions,
View,
ScrollView,
} from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import { BackBar } from "../../components/BackBar";
import { useDispatch } from "react-redux";
import {
IntroScreen,
IntroStackParamList,
} from "../../navigation/intro/StackUtil";
import { EnumFonts } from "../../types/enums/fonts.enum";
import addSplashUriToDatabase from "../../database/addSplashUriToDatabase";
import { setStarter } from "../../store/starter/actions";
import { Camera } from "expo-camera";
interface Props
extends StackScreenProps<IntroStackParamList, IntroScreen.ScanQR> {}
const ScanQRScreen: React.FC<Props> = (props) => {
const [hasPermissions, setHasPermissions] = useState(false);
const [scanned, setScanned] = useState(false);
const [processing, setProcessing] = useState(false);
const { width, height } = useWindowDimensions();
const dispatch = useDispatch();
useEffect(() => {
(async () => {
const { status } = await BarCodeScanner.requestPermissionsAsync();
setHasPermissions(status === "granted");
})();
}, []);
const handleBarCodeScanned = async ({ type, data }: BarCodeScannerResult) => {
setScanned(true);
console.log(
`Bar code with type ${type} and data ${data} has been scanned!`
);
try {
const success = await addSplashUriToDatabase(data);
if (success) {
// Navigate to other screen
dispatch(setStarter(false));
try {
const value = await AsyncStorage.setItem("#starter", "true");
if (value !== null) {
// value previously stored
// setStarter("false");
}
} catch (e) {
// error reading value
}
}
} catch (error) {
console.error(error);
Alert.alert(
"Fail to scan QR code",
"Please let us know, this could caused by invalid participant names.",
[
{
text: "OK",
},
// {text: 'Report Bug', onPress: failOnScan},
],
{ cancelable: true }
);
}
};
return (
<SafeAreaView style={{ flex: 1 }}>
<ScrollView style={{ flex: 1, padding: "5%" }}>
<BackBar goBackFunction={props.navigation.goBack} />
<View
style={{ flex: 0.3, justifyContent: "space-evenly", marginTop: 30 }}
>
<Text
style={{
fontFamily: EnumFonts.BOLD,
fontSize: 30,
marginVertical: 10,
}}
>
Scan a Splash code
</Text>
<Text
style={{
fontFamily: EnumFonts.REGULAR,
fontSize: 20,
marginVertical: 10,
}}
>
Line up the Splash code in the red square. Ensure the other phone
has its screen brightness at maximum setting.
</Text>
</View>
<View style={{ flex: 0.6, justifyContent: "space-around" }}>
{hasPermissions && (
<>
<View
style={{
width: width * 0.8,
borderWidth: 5,
borderColor: scanned ? "green" : "red",
backgroundColor: "black",
alignSelf: "center",
}}
>
<BarCodeScanner
onBarCodeScanned={scanned ? undefined : handleBarCodeScanned}
style={{
width: "100%",
height: height * 0.45,
borderWidth: 1,
borderColor: "red",
}}
/>
</View>
<Text
style={{
fontFamily: EnumFonts.REGULAR,
fontSize: 18,
textAlign: "center",
marginTop: 15,
}}
>
The square will turn green to indicate a successful scan
position
</Text>
</>
)}
{!hasPermissions && (
<Text
style={{
fontFamily: EnumFonts.MEDIUM,
fontSize: 20,
textAlign: "justify",
}}
>
Please allow us to use camera to scan qr
</Text>
)}
</View>
</ScrollView>
</SafeAreaView>
);
};
export default ScanQRScreen;
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
});
In my React Native applikation I render a <FlatList> with Images. I pass the direct imageurl as source into the <Image> Component.
<FlatList
data={this.state.images}
keyExtractor={item => item.imageToken}
renderItem={({ item }) => (
<Image key={item.imageToken} style={{ marginRight: 2, marginTop: 2, width: '50%', opacity: 1 }} source={{ uri: item.imageUrl }} alt="Alternate Text" size="xl" /> )} />
This means that the images are loaded in a different order because they are also different sizes. I would like to show a placeholder during loading.
The listAll() function resets isLoading to false before all images are displayed. Is there a 'trigger' when an image is fully visible in the view? I can't just build a single state for each image - I guess.
There will be many hundreds of pictures!
I think it's important to know that I extract the url from the google firestore images and store they as an array in state beforehand. See function getDownloadURL
Fullcode
import React, { Component } from 'react'
import { StyleSheet, SafeAreaView, ActivityIndicator } from 'react-native'
import { Image, FlatList, Center, Box } from "native-base"
import EventGalleryHeader from '../components/EventGalleryHeader.js'
import { getStorage, ref, getDownloadURL, list, listAll } from "firebase/storage"
import { LongPressGestureHandler, State } from 'react-native-gesture-handler'
export default class EventScreen extends Component {
constructor(props) {
super(props);
this.storage = getStorage()
this.pathToImages = '/eventimages/'
this.eventImageSource = this.props.route.params.eventData.key
this.imagesRef = this.pathToImages + this.eventImageSource
this.state = {
isLoading: true,
images: [],
event: {
adress: this.props.route.params.eventData.adress,
hosts: this.props.route.params.eventData.hosts,
description: this.props.route.params.eventData.description,
eventtitle: this.props.route.params.eventData.eventtitle,
invitecode: this.props.route.params.eventData.invitecode,
key: this.props.route.params.eventData.key,
timestamp: this.props.route.params.eventData.timestamp,
}
}
}
componentDidMount() {
this.getEventImageData()
}
componentWillUnmount() {
}
getEventImageData() {
const images = []
const event = {
adress: this.props.route.params.eventData.adress,
description: this.props.route.params.eventData.description,
eventtitle: this.props.route.params.eventData.eventtitle,
key: this.props.route.params.eventData.key,
timestamp: this.props.route.params.eventData.timestamp,
}
listAll(ref(this.storage, this.imagesRef))
.then((res) => {
res.items.forEach((itemRef) => {
getDownloadURL(itemRef)
.then((url) => {
const indexOfToken = url.indexOf("&token=")
const token = url.slice(indexOfToken + 7)
images.push({
"imageUrl": url,
"imageToken": token
});
this.setState({
images,
event,
isLoading: false,
});
// console.log(this.state.images)
})
.catch((error) => {
switch (error.code) {
case 'storage/object-not-found':
break;
case 'storage/unauthorized':
break;
case 'storage/canceled':
break;
case 'storage/unknown':
break;
}
});
});
}).catch((error) => {
});
}
onLongPress = (event) => {
if (event.nativeEvent.state === State.ACTIVE) {
alert("I've been pressed for 800 milliseconds");
}
};
render() {
if (this.state.isLoading) {
return (<Center style={styles.container} _dark={{ bg: "blueGray.900" }} _light={{ bg: "blueGray.50" }}>
<ActivityIndicator size="large" color="#22d3ee" />
</Center>
)
} else {
return (
<SafeAreaView style={styles.container} >
<FlatList _dark={{ bg: "blueGray.900" }} _light={{ bg: "blueGray.50" }}
style={styles.list}
numColumns={2}
ListHeaderComponent={<EventGalleryHeader data={this.state.event} />}
data={this.state.images}
keyExtractor={item => item.imageToken}
renderItem={({ item }) => (
<LongPressGestureHandler
onHandlerStateChange={this.onLongPress}
minDurationMs={800}
>
<Image key={item.imageToken} style={{ marginRight: 2, marginTop: 2, width: '50%', opacity: 1 }} source={{ uri: item.imageUrl }} alt="Alternate Text" size="xl" />
</LongPressGestureHandler>
)}
/>
</SafeAreaView>
);
}
};
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
image: {
maxHeight: 450,
width: '100%',
height: 200,
overflow: 'hidden',
},
list: {
alignSelf: 'center',
},
gallery: {
flex: 1,
width: '100%',
flexDirection: 'row',
}
})
And again it shows how important it is to read the documentation properly beforehand and to look there first if you have any questions.
You can achieve the behavior I mentioned above with the following parameters.
loadingIndicatorSource link
Similarly to source, this property represents the resource used to render the loading indicator for the image, displayed until image is ready to be displayed, typically after when it got downloaded from network.
onLoad link
Invoked when load completes successfully.
onLoadEnd link
Invoked when load either succeeds or fails.
onLoadStart link
Invoked on load start.
Example: onLoadStart={() => this.setState({loading: true})}
I am having 2 problems using React Native and Firebase Real Time Database.
When I add something to the list with the text input, all the list itens are duplicated except the item that I just added, this problem is only solved when I refresh the app screen.
When I remove something from firebase dashboard or other client, the list is not updated real time.
import React, {useState, Component} from 'react';
import {
Text,
View,
Switch,
StyleSheet,
FlatList,
TextInput,
Button,
TouchableOpacity,
SafeAreaView,
VirtualizedList,
} from 'react-native';
import database from '#react-native-firebase/database';
class MenuBreakFastScreen extends React.Component {
state = {newItem: ''};
state = {itens: []};
componentDidMount() {
let dbRef = database().ref('/cafe/itens/');
this.listenerFirebase(dbRef);
}
listenerFirebase(dbRef) {
dbRef.on('value', dataSnapshot => {
const newItens = JSON.parse(JSON.stringify(this.state.itens));
dataSnapshot.forEach(child => {
newItens.push({
name: child.val().name,
key: child.key,
});
this.setState({itens:newItens});
});
});
}
addItem() {
if (this.state.newItem === '') {
return;
}
database().ref('/cafe/itens/').push({
name: this.state.newItem,
});
this.setState({
newItem: '',
});
}
render() {
const {itens} = this.state;
const {newItem} = this.state;
const renderItem = ( {item}) => {
return(
<ItemAsset title={item.name}/>
);
}
return (
<SafeAreaView
style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
<FlatList
data={itens}
renderItem={renderItem}
keyExtractor={item => item.key}
/>
<SafeAreaView style={{flexDirection: 'row'}}>
<TextInput
style={styles.input}
onChangeText={text =>
this.setState({
newItem: text,
})
}
value={newItem}
/>
<TouchableOpacity style={styles.Botao} onPress={() => this.addItem()}>
<Text style={styles.BotaoTexto}>+</Text>
</TouchableOpacity>
</SafeAreaView>
</SafeAreaView>
);
}
}
const styles = StyleSheet.create({
texto: {
fontSize: 35,
},
input: {
color: '#000',
fontSize: 22,
borderWidth: 1,
flex: 8,
margin: 10,
},
BotaoTexto: {
color: '#fff',
fontSize: 22,
},
Botao: {
backgroundColor: '#000',
marginTop: 10,
padding: 10,
flex: 1,
alignItems: 'center',
margin: 10,
},
ListaContainer: {
flexDirection: 'row',
backgroundColor: '#000',
flex: 1,
},
item: {
backgroundColor: '#000',
padding: 20,
marginVertical: 8,
marginHorizontal: 16,
flexDirection: 'row',
},
title: {
color: '#ffff',
fontSize: 32,
},
});
const ItemAsset = ( {title} ) => {
return(
<View style={styles.item}>
<Text style={styles.title}>{title}</Text>
</View>
);
}
export default MenuBreakFastScreen;
When you are listen for real time changes on real-time database it will send all the items with snapshot when any data is changed. That happens because you are listen for whole list, not only for a single item. Therefore you do not need to get the current list from state. You just have to set the state with retrieved data.
listenerFirebase(dbRef) {
dbRef.on('value', dataSnapshot => {
const newItens = []; // This should be initially empty array. That's all.
dataSnapshot.forEach(child => {
newItens.push({
name: child.val().name,
key: child.key,
});
});
this.setState({itens:newItens});
});
}
After correcting this part the error you got when removing data will be also resolved.
I am trying to familiarize myself with React Native. At the moment I am working on an app but came across an issue when trying to display changes to the individual elements of an array. For example:
function MyApp() {
const [array, setArray] = useState([1,2,3]);
const onPress = () => {
let temp = [3,2,1];
setArray(temp);
}
return(
<View>
<TouchableHighlight onPress={onPress}>
<View>
<Text>{array[0]}</Text>
</View>
</TouchableHighlight>
</View>
);
}
With the above code, I expect '1' to be displayed in the text component and to change to '3' upon being pressed. console.log shows the state being changed but what is being displayed in the text component inside the actual app never updates. I then tried this using individual integer states like so:
const [int, setInt] = useState(0);
const onPress = () => {
setInt(1);
}
Using an integer state such as the one above works totally fine as expected. Can anyone show me what I am doing wrong with my array state? Thank you.
Your code looks perfect and should work without any issue.
Here is the slightly modified example where the first element is generated randomly and is being shown properly in the Text component.
Working Example: Expo Snack
import React, { useState } from 'react';
import { Text, View, StyleSheet, TouchableHighlight } from 'react-native';
import Constants from 'expo-constants';
export default function MyApp() {
const [array, setArray] = useState([1, 2, 3]);
const onPress = () => {
let temp = [Math.floor(Math.random() * 10), 2, 1];
setArray(temp);
};
return (
<View style={styles.container}>
<TouchableHighlight onPress={onPress}>
<View style={styles.btn}>
<Text
style={{ alignSelf: 'center', fontSize: 28, fontWeight: 'bold' }}>
{array[0]}
</Text>
</View>
</TouchableHighlight>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
},
btn: {
width: 100,
height: 100,
borderColor: 'purple',
borderWidth: 5,
justifyContent: 'center',
},
});
Your code looks to be fine, I tried the below following your code and can see that state and UI getting updated successfully when the button is being clicked.
Could you please check if your event handler function onPress is getting called, and if you are getting any error in the console when you click on it.
function App() {
const [array, setArray] = React.useState([1, 2, 3]);
const handleBtnClick = () => {
const temp = [3, 2, 1];
setArray(temp);
};
return (
<React.Fragment>
<button onClick={handleBtnClick}>Click</button>
{array.map((el) => (
<p>{el}</p>
))}
<hr />
{array[0]}
</React.Fragment>
);
}
I have this code and it works fine to shows the overlay the requested page for 5sec and Hide to shows the requested page's contents, But when the Loader indicator disappeared its (red) background still there, how to hide the background too?
It has two part firsts one for creating Loading Indicator to be hidden after 5 sec.
Working example on Expo.io:
Live Demo -
This is requested page's code: (Please, notice has to be called from /screens)
import * as React from 'react';
import { Text, View, StyleSheet } from 'react-native';
import Loader from './screens/Loader';
export default function App() {
return (
<View style={styles.container}>
<Loader /> //<--- I put the Loader here
<Text style={styles.paragraph}>
This is the requested page should be be covered by indicator background (Red color) <br/ >
The Loader disappear after 5 sec.<br />
But, its background still there!!
</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
},
paragraph: {
margin: 24,
fontSize: 18,
fontWeight: 'bold',
textAlign: 'center',
},
});
And the Loader.js code is :
import React, { Component } from 'react';
import { ActivityIndicator, View, Text, TouchableOpacity, StyleSheet } from 'react-native';
class Loader extends Component {
state = { animating: true }
closeActivityIndicator = () => setTimeout(() => this.setState({
animating: false }), 5000)
componentDidMount = () => this.closeActivityIndicator()
render() {
const animating = this.state.animating
return (
<View style = {styles.container}>
<ActivityIndicator
animating = {animating}
color = '#bc2b78'
size = "large"
style = {styles.activityIndicator}/>
</View>
)
}
}
export default Loader
const styles = StyleSheet.create ({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
marginTop: 0,
position: 'absolute',
height: "100%",
width: "100%",
backgroundColor: 'red',
opacity: 0.7
},
activityIndicator: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
}
})
The problem is you are always rendering the Loader and its always visible, so the easiest way to handle this would be to return null and hide the loader when its not necessary like below.
render() {
const animating = this.state.animating;
if(!this.state.animating)
return null;
return (
<View style = {styles.container}>
<ActivityIndicator
animating = {animating}
color = '#bc2b78'
size = "large"
style = {styles.activityIndicator}/>
</View>
)
}