Console.log() inside useEffect hook in React Native - javascript

Iam novice to react native programming (programming in genera) and for a project i have created a functional react component to display list of events data. In this component i want to sort the events first based on year and month and then based on days within these days. I created the algorithm for sorting the events and pasted the code for this on the useEffect hook of this react component. This component code is given below
import { ActivityIndicator, Dimensions, StyleSheet, Text, View } from 'react-native'
import React, { useContext, useEffect, useLayoutEffect, useState } from 'react'
import { Ionicons } from '#expo/vector-icons';
import { ScrollView, TouchableOpacity } from 'react-native-gesture-handler';
import { EventContext } from '../context/EventContext';
import EventCard from '../components/EventCard';
import { headerStyleOptions } from '../styles/headerStyleOptions';
import H1 from '../styles/styledcomponents/H1';
import { PRIMARYCOLOR } from '../styles/colors';
import { StatusBar } from 'expo-status-bar';
const { height, width } = Dimensions.get('window');
const monthsArray = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
let eventSorted: Object[] = [];
const EventListScreen = ({ navigation }) => {
const { state: events } = useContext(EventContext);
const [loading, setLoading] = useState(true);
useLayoutEffect(() => {
navigation.setOptions(headerStyleOptions('drawer', navigation, 'EVENTS LIST',
{ rightIcon1: 'add-circle', rightIconOnPress1: () => { navigation.navigate('EventCreateScreen') }, textColor: PRIMARYCOLOR, fontFamilly: 1, backgroundColor: 'black' }))
}, [])
useEffect(() => {
eventSorted = [];
events.map(ev => {
const date = new Date(ev.deadlinedate)
let key = `${date.getFullYear()}${date.getMonth()}`;
const dayKey = `${date.getDate()}`
if (eventSorted.length == 0) {
let obj: Object = {}
obj[key] = {};
eventSorted.push(obj)
eventSorted[0][key][dayKey] = []
eventSorted[0][key][dayKey].push(ev);
} else {
const check = eventSorted.findIndex(evso => ((Object.keys(evso)[0] == key)))
if (check == -1) {
let obj: Object = {}
obj[key] = {};
for (let i = 0; i < eventSorted.length; i++) {
if (date.getFullYear() == parseInt(Object.keys(eventSorted[i])[0].slice(0, 4))) {
if (date.getMonth() < parseInt(Object.keys(eventSorted[i])[0].slice(4))) {
eventSorted.splice(i, 0, obj)
eventSorted[i][key][dayKey] = []
eventSorted[i][key][dayKey].push(ev)
i = i + 1;
break;
} else {
if (i == eventSorted.length - 1) {
eventSorted.splice(i + 1, 0, obj)
eventSorted[i + 1][key][dayKey] = []
eventSorted[i + 1][key][dayKey].push(ev)
break;
}
}
} else if (date.getFullYear() < parseInt(Object.keys(eventSorted[i])[0].slice(0, 4))) {
eventSorted.splice(i, 0, obj)
eventSorted[i][key][dayKey] = []
eventSorted[i][key][dayKey].push(ev)
break;
}else{
if(i==eventSorted.length-1){
eventSorted.splice(i+1,0,obj)
eventSorted[i+1][key][dayKey] = [];
eventSorted[i+1][key][dayKey].push(ev)
break;
}
}
}
} else {
if (dayKey in eventSorted[check][key]) {
eventSorted[check][key][dayKey].push(ev)
} else {
Object.entries(eventSorted[check][key]).forEach(([k, value], index) => {
if (parseInt(k) > parseInt(dayKey)) {
let keyValues = Object.entries(eventSorted[check][key]);
keyValues.splice(index, 0, [dayKey, []])
eventSorted[check][key] = Object.fromEntries(keyValues)
eventSorted[check][key][dayKey].push(ev)
return false;
} else {
if (index == Object.keys(eventSorted[check][key]).length - 1) {
let keyValues = Object.entries(eventSorted[check][key]);
keyValues.splice(index + 1, 0, [dayKey, []])
eventSorted[check][key] = Object.fromEntries(keyValues)
eventSorted[check][key][dayKey].push(ev)
return false;
}
}
}
)
}
}
}
})
setTimeout(()=>{
console.log('finished2')
setLoading(false)
},0)
console.log('finished1')
console.log('eventSorted', eventSorted)
}, [])
return (
<View style={{ height,backgroundColor:'white'}}>
<StatusBar style='inverted' />
{loading ?
<View style={{flex:1,alignItems:'center',justifyContent:'center'}}>
<ActivityIndicator size='large'/>
</View>
:
// <Text>Hello</Text>
<ScrollView>
{
eventSorted.map(ev => {
return Object.keys(ev).map(key => {
let month: string;
month = monthsArray[parseInt(key.toString().slice(4))]
return (
<View>
<H1 key={`${month} ${key.substring(0,4)}`} style={{ alignSelf: 'center', fontSize: 22, paddingVertical: '2%' }}>{month} {key.substring(0, 4)}</H1>
<View>
{
Object.keys(ev[key]).map(k => {
let dateheight = height * .2 * Object.keys(ev[key][k]).length
return (
<View key={`${month} ${key.substring(0,4)} ${k}`}>
<View style={{ position: 'absolute', height: dateheight, width: 50, borderBottomWidth: 1, borderRightWidth: 1, backgroundColor: 'white' }}>
<Text style={{ alignSelf: 'center', fontSize: 20, top: 30 }}>{k}</Text>
</View>
{
ev[key][k].map(so => {
return <EventCard eventid={so.eventid} key={so.eventid}/>
})
}
</View>
)
}
)
}
</View>
</View>
)
})
})
}
<View style={{ alignSelf: 'center', backgroundColor: 'white', width: '100%' }}>
<Text style={{ fontSize: 100 }}></Text>
</View>
</ScrollView>
}
</View>
)
}
export default EventListScreen
const styles = StyleSheet.create({})
After running this code when i open this page there is a time lag to display the sorted event list. For checking where the code is taking time. I put a console.log on multiple locations in the code. You can see two console.logs at the end of the useEffect in adjacent two line which are console.log('finished1') and console.log('eventSorted', eventSorted). After inserting these two console logs when I run the page, I saw that finished1 is logging to the console immediately after navigating to this page but the next console.log is getting delayed like it is being awaited and it gets console logged and view gets populated at almost the same time. Can anyone tell me why eventSorted console log is behaving is it being awaited on?
Also When i interchange the position of the first console.log and second console.log. Then this console.log('finished1') is logging after a long delay after console.log('eventSorted',eventSorted) is output to the terminal.

You may be experiencing an adverse effect of not clearing setTimeout inside of useEffect - which is known to produce strange behavior.
Instead of
setTimeout(()=>{
console.log('finished2')
setLoading(false)
},0)
console.log('finished1')
console.log('eventSorted', eventSorted)
try
const timer = setTimeout(() => {
console.log("Hello, World!"), 3000);
setLoading(false)
},1000)
console.log('finished1')
console.log('eventSorted', eventSorted)
return () => clearTimeout(timer);
Also notice I have set the delay in setTimeout to 1 second (you had the delay set to 0 seconds in your code above).

Related

undefined is not an object (evaluating 'selectedDoc.quantity')

I am constantly getting this error - This issue is in remove case. as soon as I remove the remove case in Appoint/reducers, the code is working. can someone help me find what the error is in remove case.
undefined is not an object (evaluating 'selectedDoc.quantity')
I don't know why it's undefined. help will be appreciated.
//appoint.js/reducers
import { APPOINT_DOC,Remove_Doc } from "../actions/appoint";
import docAppoint from '../../modules/docAppoint';
const initialState = {
items: {},
totalAmount: 0
};
export default (state = initialState, action) => {
switch (action.type) {
case APPOINT_DOC:
const addedProduct = action.product;
const prodPrice = addedProduct.price;
const prodTitle = addedProduct.title;
let updateOrNewDocAppoint;
if (state.items[addedProduct.id]) {
// already have the item in the cart
updateOrNewDocAppoint = new docAppoint(
state.items[addedProduct.id].quantity + 1,
prodPrice,
prodTitle,
state.items[addedProduct.id].sum + prodPrice
);
} else {
updateOrNewDocAppoint = new docAppoint(1, prodPrice, prodTitle, prodPrice);
}
return {
...state,
items: { ...state.items, [addedProduct.id]: updateOrNewDocAppoint },
totalAmount: state.totalAmount + prodPrice
};
case Remove_Doc:
const selectedDoc = state.items[action.pid];
const quantity = selectedDoc.quantity;
let updatedDoc;
if (quantity > 1) {
// need to reduce it, not erase it
const updatedDoc = new docAppoint(
selectedDoc.quantity - 1,
selectedDoc.prodPrice,
selectedDoc.prodTitle,
selectedDoc.sum - selectedDoc.prodPrice
);
updatedDoc = { ...state.items, [action.pid]: updatedDoc };
} else {
updatedDoc = { ...state.items };
delete updatedDoc[action.pid];
}
return {
...state,
items: updatedDoc,
totalAmount: state.totalAmount - selectedDoc.prodPrice
};
}
//appoint.js/actions
export const APPOINT_DOC = 'APPOINT_DOC';
export const Remove_Doc = 'Remove_Doc';
export const appointDoc = product => {
return {
type : APPOINT_DOC, product:product
};
};
export const removeDoc = productId => {
return{ type: Remove_Doc, pid: productId };
};
//docAppoint.js/modules
class docAppoint {
constructor( quantity,productPrice,productTitle,sum){
this.quantity=quantity;
this.productPrice=productPrice;
this.productTitle=productTitle;
this.sum=sum;
}
}
//registerDr
import React from "react";
import { View, Text, StyleSheet,Button,FlatList } from 'react-native';
import {useSelector,useDispatch} from 'react-redux';
import colors from "../../constants/colors";
import appoint from "../../store/reducers/appoint";
import ProcessDr from "../../components/Doctor/process";
import * as docActions from '../../store/actions/appoint';
const registerDr = props => {
const totalFee = useSelector (state => state.appoint.totalAmount)
const appointDoc = useSelector (state => {
const transformedAppointDoc = []
for (const key in state.appoint.items ) {
transformedAppointDoc.push({
productId : key,
productTitle : state.appoint.items[key].productTitle,
productPrice : state.appoint.items[key].productPrice,
quantity : state.appoint.items[key].quantity,
sum : state.appoint.items[key].sum
});
}
return transformedAppointDoc;
});
const dispatch = useDispatch();
return(
<View style={styles.screen}>
<View style={styles.summary}>
<Text style={styles.summarytext}> Fee: <Text style={styles.amount} > {totalFee} RS </Text> </Text>
<Button disabled = {appointDoc.length === 0}
title="Appoint Doctor" color={colors.primary} />
</View>
<View>
<FlatList data ={appointDoc}
keyExtractor = {item => item.productId}
renderItem ={itemData =>
<ProcessDr quantity={itemData.item.quantity}
title={itemData.item.productTitle}
amount={itemData.item.sum}
onRemove = {() => {
dispatch(docActions.removeDoc(itemData.item.productTitle))
}}
/>} />
</View>
</View>
)
}
const styles= StyleSheet.create({
screen:{
margin: 20,
},
summary:{
flexDirection:'row',
alignItems:'center',
justifyContent:'space-between',
marginBottom:20,
padding: 10,
shadowColor: 'black',
shadowOpacity: 0.26,
shadowOffset: { width: 0, height: 2 },
shadowRadius: 8,
elevation: 5,
borderRadius: 10,
backgroundColor: 'white',
},
summarytext:{
fontFamily:'opsb',
fontSize:18,
},
amount: {
color: colors.primary
}
})
export default registerDr;
at first glance, undefined is not an object (evaluating 'selectedDoc.quantity') is referring to the selectedDoc is undefined.
Have you debugged to see if the items has the the correct key that matches action.pid ?
const selectedDoc = state.items[action.pid];
const quantity = selectedDoc.quantity;
do nothing if undefined.
const selectedDoc = state.items[action.pid];
if (selectedDoc) {
// your logic here
} else {
return state; // do nothing
}

Need help for playing Audiofiles in sequence for talking clock - react-native

I have rediscovered React Native and would like to learn how to develop a native app. For learning, I will create a speaking clock.
The time and date are already programmed, shown on-screen, and are updated every second.
I want one button to announce the current time and the second, the date.
I have the audio files with speaking numbers from 00 to 59. The words "It is", "and" and "o'clock".
I have problems understanding the audio part. I can play individual audio files, but I can't get multiple files to be played one after the other.
Does anyone have an idea or a solution?
My previous code looks like this:
import React from 'react';
import type { Node } from 'react';
import {
SafeAreaView,
ScrollView,
StatusBar,
StyleSheet,
Text,
useColorScheme,
View,
Button,
Alert,
NativeModules,
} from 'react-native';
import { Colors } from 'react-native/Libraries/NewAppScreen';
//Audio-Library
import useSound from 'react-native-use-sound';
const Section = ({ children, title }): Node => {
const isDarkMode = useColorScheme() === 'dark';
return (
<View style={styles.sectionContainer}>
<Text
style={[
styles.sectionTitle,
{
color: isDarkMode ? Colors.white : Colors.black,
},
]}
>
{title}
</Text>
<Text
style={[
styles.sectionDescription,
{
color: isDarkMode ? Colors.light : Colors.dark,
},
]}
>
{children}
</Text>
</View>
);
};
function Wochentag(tag = 0) {
var days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
return days[tag];
}
function Monat(m = 0) {
var months = [
'',
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December',
];
return months[m];
}
function Digit(d = 0) {
if (d < 10) d = '0' + d;
return d;
}
class Uhr extends React.Component {
constructor(props) {
super(props);
this.state = {
time: Digit(new Date().getHours()) + ':' + Digit(new Date().getMinutes()),
};
}
componentDidMount() {
this.intervalID = setInterval(() => this.tick(), 1000);
}
componentWillUnmount() {
clearInterval(this.intervalID);
}
tick() {
this.setState({
time: Digit(new Date().getHours()) + ':' + Digit(new Date().getMinutes()),
});
}
render() {
return <Text style={styles.textStyle}>{this.state.time}</Text>;
}
}
class Datum extends React.Component {
constructor(props) {
super(props);
this.state = {
datum: Digit(new Date().getDate()) + ' ' + Monat(new Date().getMonth() + 1) + ' ' + new Date().getFullYear(),
wtag: Wochentag(new Date().getDay()),
};
}
componentDidMount() {
this.intervalID = setInterval(() => this.tick(), 1000);
}
componentWillUnmount() {
clearInterval(this.intervalID);
}
tick() {
this.setState({
datum: Digit(new Date().getDate()) + ' ' + Monat(new Date().getMonth() + 1) + ' ' + new Date().getFullYear(),
wtag: Wochentag(new Date().getDay()),
});
}
render() {
return (
<Text style={styles.textStyle}>
{this.state.wtag}, {this.state.datum}
</Text>
);
}
}
const App: () => Node = () => {
const isDarkMode = useColorScheme() === 'dark';
const backgroundStyle = {
backgroundColor: isDarkMode ? Colors.darker : Colors.lighter,
};
//Play Audio
const path = 'test.wav';
const [play, pause, stop, data] = useSound(path, {
timeRate: 100,
playbackRate: 1000,
volume: 1.0,
});
const onPress = () => {
if (!data.isPlaying) play();
else {
stop();
}
};
const PlayFile = <Button title={data.isPlaying ? 'Pause' : 'Play'} onPress={() => onPress()} />;
return (
<SafeAreaView style={backgroundStyle}>
<StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} />
<ScrollView contentInsetAdjustmentBehavior="automatic" style={backgroundStyle}>
<View
style={{
backgroundColor: isDarkMode ? Colors.black : Colors.white,
}}
>
<Section title="Aktuelle Uhrzeit ist: ">
<Uhr /> Uhr
</Section>
<Section title="Heute ist: ">
<Datum />
</Section>
<Section title=""></Section>
<Button
title="Uhrzeit ansagen"
onPress={() => Alert.alert('Noch wird die Uhrzeit nicht angesagt. das kommt aber sehr bald....')}
/>
<Button
title="Datum ansagen"
onPress={() => Alert.alert('Noch wird das Datum nicht angesagt. das kommt aber sehr bald....')}
/>
{PlayFile}
</View>
</ScrollView>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
sectionContainer: {
marginTop: 32,
paddingHorizontal: 24,
},
sectionTitle: {
fontSize: 24,
fontWeight: '600',
},
sectionDescription: {
marginTop: 8,
fontSize: 18,
fontWeight: '400',
},
highlight: {
fontWeight: '700',
},
});
export default App;
Thanks in advance!
Update:
Maybe someone has an idea how to use the following example for my problem.
HTML5 audio tag in React
New Update:
this code is working, but not optimal. Have someone an Idea for better solution?
import Sound from 'react-native-sound';
function Digit(d=0) {
if(d < 10) d = '0' + d;
return d;
}
function AudioClock(datei) {
Sound.setCategory('Playback');
var audioFile = new Sound(datei, Sound.MAIN_BUNDLE, (error) => {
if (error) {
console.log('failed to load the sound', error);
return;
}
// loaded successfully
console.log('duration in seconds: ' + audioFile.getDuration() + 'number of channels: ' + audioFile.getNumberOfChannels());
audioFile.setVolume(1);
audioFile.setPan(1);
audioFile.play((success) => {
if (success) {
console.log('successfully finished playing');
} else {
console.log('playback failed due to audio decoding errors');
}
});
});
...
const SayTime = () => {
setTimeout( () => AudioClock('it_is.wav'), 10); // it is
setTimeout( () => AudioClock('uhr_' + Digit(new Date().getHours()) +'.wav'), 900); // hours
setTimeout(() => AudioClock('and.wav'), 1400); // and
setTimeout( () => AudioClock('uhr_' + Digit(new Date().getMinutes()) + '.wav'), 2000); // minutes
setTimeout( () => AudioClock('o_clock.wav'), 3100); // o'clock
};
...
<Button
title="Say time"
onPress={SayTime}
/>`

FlatList scrollToIndex out of range

I have a FlatList where I'm trying to scroll through each index of my data array every X amount of seconds. There's only two items in my array right now but there could be more. The current code works for the first two iterations but then it does not seem to reset properly and I get the scrollToIndex out of range error: index is 2 but maximum is 1. I would think that when the currentIndex is >= data.length my if statement would setCurrentIndex back to 0 but it doesn't seem to work. Basically what I'm trying to do is loop the items in the Flatlist automatically but each item pausing for a few seconds.
/**
* Sample React Native App
* https://github.com/facebook/react-native
*
* #format
* #flow strict-local
*/
import 'react-native-gesture-handler';
import React, {useState, useEffect, useRef} from 'react';
import { NavigationContainer } from '#react-navigation/native';
import { createStackNavigator, HeaderBackButton } from '#react-navigation/stack';
import {
SafeAreaView,
StyleSheet,
ScrollView,
View,
Text,
StatusBar,
ImageBackground,
Image,
TextInput,
Button,
TouchableNativeFeedback,
TouchableWithoutFeedback,
TouchableOpacity,
Modal,
Pressable,
PanResponder,
FlatList,
Dimensions
} from 'react-native';
import { Immersive } from 'react-native-immersive';
import {
Header,
LearnMoreLinks,
Colors,
DebugInstructions,
ReloadInstructions,
} from 'react-native/Libraries/NewAppScreen';
import WineList from './screens/WineList';
import Home from './screens/Home';
import Rate from './screens/Rate';
import Thankyou from './screens/Thankyou';
const Stack = createStackNavigator();
const { width: windowWidth, height: windowHeight } = Dimensions.get("window");
const wineclub = require('./images/wineclub.png');
const gaspers = require('./images/gaspers.png');
const qrcode = require('./images/wineclubQR.png');
let ads = [
{
adImg: wineclub,
adTitle: 'Space will be limited so join online today!',
adInfo: ' Upon joining, both clubs will be billed our Trio Pre-Opening Promotion',
qrCodeImg: qrcode
},
{
adImg: gaspers,
adTitle: 'Coming Soon!',
adInfo: 'Gourmet chef designed menu. Stunning views. Modern romantic decor',
qrCodeImg: qrcode
}
]
function AdSlider({data}){
return(
<View style={{alignContent:'center', alignItems:'center', backgroundColor:'#4B4239', height:1400}}>
<Image source={data.adImg} style={{width:640,height:500}} ></Image>
<Text style={{color:'white', fontFamily:'LaoMN', fontSize:30, marginTop:20}}>{data.adTitle}</Text>
<Text style={{color:'white', fontFamily:'LaoMN', fontSize:20, marginTop:20, textAlign:'center'}} > {data.adInfo} </Text>
<View style={{flexDirection:'row', justifyContent:'flex-start', alignContent:'center', alignItems:'center', marginTop:20}}>
<Text style={{fontSize:40, color:'white', padding:20}}>Scan Here </Text>
<Image source={data.qrCodeImg}></Image>
</View>
</View>
)
}
const App: () => React$Node = () => {
Immersive.on()
Immersive.setImmersive(true)
const navigationRef = useRef(null);
const myRef = useRef(null);
const currentIndex = useRef(0);
const [modalVisible, setModalVisible] = useState(false);
const timerId = useRef(false);
const [timeForInactivityInSecond, setTimeForInactivityInSecond] = useState(
5
)
useEffect(() => {
resetInactivityTimeout()
},[])
const panResponder = React.useRef(
PanResponder.create({
onStartShouldSetPanResponderCapture: () => {
// console.log('user starts touch');
setModalVisible(false)
resetInactivityTimeout()
},
})
).current
const resetInactivityTimeout = () => {
clearTimeout(timerId.current)
timerId.current = setTimeout(() => {
// action after user has been detected idle
setModalVisible(true)
navigationRef.current?.navigate('Home');
}, timeForInactivityInSecond * 1000)
}
// for the slider
useEffect(() => {
const timer = setInterval(() => {
currentIndex.current = currentIndex.current === ads.length - 1
? 0
: currentIndex.current + 1;
myRef.current.scrollToIndex({
animated: true,
index: currentIndex.current ,
});
}, 5000);
return () => clearInterval(timer);
}, []);
return (
<NavigationContainer ref={navigationRef} >
<View {...panResponder.panHandlers} style={{ flex:1}}>
<TouchableWithoutFeedback >
<Modal
animationType="slide"
transparent={false}
hardwareAccelerated={false}
visible={modalVisible}
>
<FlatList
ref={myRef}
data={ads}
renderItem={({ item, index }) => {
return <AdSlider key={index} data={item} dataLength={ads.length} />;
}}
pagingEnabled
horizontal
showsHorizontalScrollIndicator={false}
/>
</Modal>
</TouchableWithoutFeedback>
<Stack.Navigator navigationOptions={{headerTintColor: '#ffffff',}} screenOptions={{
headerTintColor: '#ffffff',
cardStyle: { backgroundColor: '#4B4239' },
}} >
<Stack.Screen name="Home"
component={Home} options={{
headerShown: false,
}} />
<Stack.Screen name="WineList" component={WineList} options={{
title: 'Exit',
headerStyle: {
backgroundColor: '#4B4239',
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold',
},
}}/>
<Stack.Screen name="Rate" component={Rate} options={{
title: 'Back to Selections',
headerStyle: {
backgroundColor: '#4B4239',
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold',
},
}}/>
<Stack.Screen name="Thankyou" component={Thankyou}
options={
{
headerShown: false,
title: 'Home',
headerStyle: {
backgroundColor: '#4B4239',
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold',
},
}}/>
</Stack.Navigator>
</View>
</NavigationContainer>
);
};
export default App;
You're getting this error because you are passing the item as data to the AdSlider component and it does not have any length property of course thus it returns undefined for data.length and that does not evaluate the expression currentIndex === data.length - 1 which it becomes currentIndex === undefined - 1 thus currentIndex will get increased by 1 without stopping and it will reach the value of 2 which is out of bounds.
There are several issues with your code.
You should not have a component inside another component and especially not when using effects and state from the parent component. Remove AdSlider outside of the App component.
You are passing item as data to the AdSlider and you are trying to fetch that as the data.length, which is obvious that it's not going to work because the data is the item which is an object and not an array.
You don't need to use the effects inside the AdSlider, set just one effect inside the App and change currentIndex to be a ref instead of a state variable because you don't need it's changing state in order to re-render because you're calling scrollToIndex for forcing the list to update and re-render.
Making it work using state and setTimeout
If you want to make the code wotk with currentIndex being in state (which you don't need), you can move effects inside the App component and change data.length with ads.length and it will work.
const App: () => React$Node = () => {
Immersive.on()
Immersive.setImmersive(true)
const navigationRef = useRef(null);
const myRef = useRef(null);
const [currentIndex, setCurrentIndex] = useState(0);
useEffect(() => {
myRef.current.scrollToIndex({
animated: true,
index: currentIndex ,
});
}, [currentIndex]);
useEffect(()=> {
const timer = setTimeout(()=> {
// Change data.length to ads.length here
const nextIndex = currentIndex === ads.length - 1
? 0
: currentIndex + 1;
setCurrentIndex(nextIndex);
}, 5000);
return () => clearTimeout(timer);
}, [currentIndex]);
...
}
Making it work using ref and setInterval
Best thing to do though, is to convert currentIndex to a be a ref and use setInterval instead of setTimeout to have a looping timer call every 5 seconds:
const App: () => React$Node = () => {
Immersive.on()
Immersive.setImmersive(true)
const navigationRef = useRef(null);
const myRef = useRef(null);
// Make currentIndex a ref instead of a state variable,
// because we don't need the re-renders
// nor to trigger any effects depending on it
const currentIndex = useRef(0);
useEffect(() => {
// Have a timer call the function every 5 seconds using setInterval
const timer = setInterval(() => {
// Change data.length to ads.length here
currentIndex.current = currentIndex.current === ads.length - 1
? 0
: currentIndex.current + 1;
myRef.current.scrollToIndex({
animated: true,
index: currentIndex.current,
});
}, 5000);
return () => clearInterval(timer);
}, []);
...
}
You can check a working Expo Snack here.
looks like your if statement is incorrect, the maximum index should be totalLength - 1.
for example, we have an array of 3 items: [{id: 1, index: 0}, {id: 2, index: 1}, {id: 3, index: 2}], then the length of the array is 3, but the maximum index is 2, so when the current index is ">= 2 (totalLength - 1)", you should reset it to 0. and for the else conditions, set next index to 'currentIdx + 1'
if(activeIdx === ITEMS.length - 1){
setActiveIdx(0)
} else {
setActiveIdx(idx => idx + 1);
}
for more detailed, code may look like this:
function Slider(props) {
const ref = React.useRef(null);
const [activeIdx, setActiveIdx] = React.useState(0)
React.useEffect(() => {
ref.current.scroll({left: ITEM_WIDTH * activeIdx, behavior: "smooth"}) // please use .scrollToIndex here
}, [activeIdx]);
React.useEffect(() => {
let timer = setTimeout(() => {
let nextIdx = activeIdx === ITEMS.length - 1 ? 0 : activeIdx + 1;
setActiveIdx(nextIdx)
}, 3000);
return () => clearTimeout(timer)
}, [activeIdx]);
return (...)
}

How to convert 1d into 2d and store it in a react native state?

I have an array,
const array = [
{"row":"0", "column": "0"},
{"row":"0", "column": "1"},
{"row":"1", "column": "0"},
{"row":"1", "column": "1"},
{"row":"2", "column": "0"},
{"row":"2", "column": "1"}
]
Note : the rows and columns arent predefined, it may change every time.
From the array i need to create a 2D array and store it on a state in react native, so that i can use the state to render the requirements using the array stored,
I have two problems, one is converting 1d into 2d and another one is storing it into an state for further usage,
My 1d to 2d converting function:
for (let i = 0; i < this.state.array.length; i++) {
let row = this.state.array[i].row;
if (newSeats[row] === undefined) newSeats[row] = [];
newSeats[row].push(this.state.array[i]);
}
for (let i = 0; i < this.state.array.length; i++) {
let column = this.state.array[i].column;
if (newSeatss[column] === undefined) newSeatss[column] = [];
newSeatss[column].push(this.state.array[i]);
}
It gives two arrays, so is this efficient way to convert 1d to 2d ? And i cant use setstate here as it throws error saying maximum depth reached for setstate, So how to store the result in a state ? and if there is any other better way make the conversion please suggest !
And my whole component :
import React, { Component} from 'react';
import { View, Text, StyleSheet, ScrollView, Dimensions, TouchableOpacity, FlatList } from 'react-native';
import { connect } from 'react-redux';
import { fetchSeatLayout } from '../../actions/HomeActions';
import Loader from '../components/loader';
import { Feather } from '#expo/vector-icons';
import { Button } from 'react-native-elements';
const w = Dimensions.get('window').width;
const h = Dimensions.get('window').height;
class SeatLayout extends Component {
state = { isLoading: true, selectedSeatLayouts: [], seatArray: [] }
componentDidMount() {
this.props.fetchSeatLayout(this.props.selectedBus.id, (data) => {
this.setState({ isLoading : false, selectedSeatLayouts: data.seats })
})
}
seats = () => {
let arr2d = this.state.selectedSeatLayouts.reduce((r, {column, row, name }) => {
if (!r[row]) r[row] = [];
r[row][column] = name;
return r;
}, []);
console.log(arr2d);
}
render() {
return(
<View style={styles.container}>
<Loader
loading={this.state.isLoading} />
<View style={{ flex: 1 }}>
<View style={styles.headerContainer}>
<View style={{ paddingTop: 50, paddingHorizontal: 20, alignItems: 'center' }}>
<Text>30 Sep, 2020</Text>
</View>
</View>
<View style={styles.bodyContainer}>
{this.seats()}
</View>
</View>
</View>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff'
},
headerContainer:{
height: h * 0.23,
backgroundColor: '#f0eff4',
},
bodyContainer:{
justifyContent: 'center'
},
});
const mapStateToProps = state => {
return {
selectedBus: state.home.userSelectedBus,
}
}
export default connect(mapStateToProps, {fetchSeatLayout})(SeatLayout);
Update your componentDidMount to this, so it saves the 2D array in the state
componentDidMount() {
this.props.fetchSeatLayout(this.props.selectedBus.id, (data) => {
this.setState({ isLoading : false, selectedSeatLayouts: data.seats},()=>{
this.setState({seatArray:this.seats()})
})
})
}
Right now seats function isn't returning anything, please update it to return something
seats = () => {
let arr2d = this.state.selectedSeatLayouts.reduce((r, {column, row, name }) => {
if (!r[row]) r[row] = [];
r[row][column] = name;
return r;
}, []);
console.log(arr2d);
return arr2d;
}
At this point you have a state with a parameter seatArray, you can use it as per your requirement now.
Update this code block to show something instead of calling 2darr,
<View style={styles.bodyContainer}>
{this.seats()} //remove this to show some visual component
</View>

How come my value gets reset to 0 when I change screen if I am using async storage?

I am using a stack Navigator, my main screen is tracker.js, and a second screen macros.js
On macros.js I can add nutritional macros (calories, Fats, Carbs, Protein) manually, and add it to my UsedDailyCalories. However when I go back to my tracker.js, which is automatically got 0 calories and I return to macros.js, the value goes back down to 0. And I am not sure why async storage is not working. Here is my code:
import React from "react";
import {
StyleSheet,
View,
Button,
Text,
TextInput,
Modal,
Animated,
} from "react-native";
import { TouchableOpacity } from "react-native-gesture-handler";
import AsyncStorage from "#react-native-community/async-storage";
import {useDispatch} from "react-redux";
export default class Macros extends React.Component {
static navigationOptions = {
title: "My Macros",
};
constructor(props) {
super(props);
this.getData();
this.state = {
isLoading: true,
dataSource: null,
totalCalsSet: 0,
showModal: false,
showModal2: false,
UsedDailyCalories: 0,
UsedDailyFat: +this.props.navigation.getParam("totalFat", "nothing sent"),
UsedDailyCarbs: 0,
UsedDailyProtein: 0,
CalsFatInput: 0,
CalsProteinInput: 0,
CalsCarbsInput: 0,
CaloriePercentage: 0,
};
let calsTakenFromTracker = this.props.navigation.getParam("totalCal", "nothing sent");
this.state.UsedDailyCalories += calsTakenFromTracker;
}
setMacroGoalModal = () => {
this.setState({
showModal: true,
});
};
AddMacrosModal = () => {
this.setState({
showModal2: true,
});
};
addMacrosManually = (ProteinInput, FatInput, CarbsInput) => {
let CalsProteinInput = ProteinInput * 4;
let CalsFatInput = FatInput * 9;
let CalsCarbsInput = CarbsInput * 4;
let CalsCalorieInput = CalsCarbsInput + CalsFatInput + CalsProteinInput;
let withAddedCalories = this.state.UsedDailyCalories + CalsCalorieInput;
this.setState({
UsedDailyCalories :withAddedCalories,
UsedDailyFat: +FatInput,
UsedDailyCarbs: +CarbsInput,
UsedDailyProtein: +ProteinInput,
showModal2: false,
});
console.log(this.state.UsedDailyCalories);
const firstPair = ["UsedTotalCalories", JSON.stringify(this.state.UsedDailyCalories)];
const secondPair = ["UsedTotalCarbs", JSON.stringify(this.state.UsedDailyCarbs)];
const thirdPair = ["UsedTotalProtein", JSON.stringify(this.state.UsedDailyProtein)];
const fourthPair = ["UsedTotalFat", JSON.stringify(this.state.UsedDailyFat)];
try {
this.setState({});
var usedValues = [firstPair, secondPair, thirdPair, fourthPair];
AsyncStorage.setItem("DATA_KEY", JSON.stringify(usedValues))
} catch (error) {
console.log(error);
}
};
setMacros = async (ProteinInput, FatInput, CarbsInput) => {
let CalsProteinInput = ProteinInput * 4;
let CalsFatInput = FatInput * 9;
let CalsCarbsInput = CarbsInput * 4;
let totalCalsSet = CalsCarbsInput + CalsFatInput + CalsProteinInput;
let CaloriePercentage = (totalCalsSet / 2400) * 100;
this.setState({
totalCalsSet: totalCalsSet,
CalsProteinInput: ProteinInput,
CalsFatInput: FatInput,
CalsCarbsInput: CalsCarbsInput,
showModal: false,
CaloriePercentage: CaloriePercentage,
});
console.log(totalCalsSet);
const firstPair = ["totalCalsSet", JSON.stringify(this.state.totalCalories)];
const secondPair = ["totalCarbsSet", JSON.stringify(CarbsInput)];
const thirdPair = ["totalProteinSet", JSON.stringify(ProteinInput)];
const fourthPair = ["totalFatSet", JSON.stringify(FatInput)];
try {
this.setState({});
var setValues = [firstPair, secondPair, thirdPair, fourthPair];
AsyncStorage.setItem("DATA_KEY", JSON.stringify(setValues))
} catch (error) {
console.log(error);
}
};
getData = async () => {
try {
AsyncStorage.multiGet(["key1", "key2"]).then(response => {
})
} catch(e) {
// read error
}
};
render() {
const { navigate } = this.props.navigation;
let CaloriePercentage = this.state.CaloriePercentage + "%";
return (
//styling for navigation container
<View style={styles.container}>
<View style={styles.topStyle}>
<Text>{this.state.UsedDailyCalories} </Text>
<Text>{this.state.UsedDailyCarbs} </Text>
<View style={styles.setMacros}>
<TouchableOpacity onPress={() => this.setMacroGoalModal()}>
<Text> Set Daily Macro Goal </Text>
</TouchableOpacity>
</View>
<View>
<TouchableOpacity style={styles.setMacros} onPress={() => this.AddMacrosModal()}>
<Text> add Daily Macro Goal </Text>
</TouchableOpacity>
</View>
<View style={styles.viewOfMacros}>
<Text>Cals: {this.state.totalCalsSet}</Text>
<Text>{Math.floor(this.state.CaloriePercentage)}%</Text>
<View style={styles.progressBar}>
<Animated.View
style={
([StyleSheet.absoluteFill],
{ backgroundColor: "#8BED4F", width: CaloriePercentage })
}
/>
</View>
<Text>Fat: {this.state.CalsFatInput}</Text>
<Text>Carbs: {this.state.CalsCarbsInput}</Text>
<Text>Protein: {this.state.CalsProteinInput}</Text>
</View>
<View>
setState is asynchronous, so if you call setState and then immediately try to access its values they will be old.
You can either use the callback available as the second argument to setState, check that out here or you can just use the values you used to set your state and pass those to your file system for storage.
Let me know if that doesn't make enough sense.

Categories