FlatList scrollToIndex out of range - javascript

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 (...)
}

Related

Console.log() inside useEffect hook in React Native

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).

Why is the state changing by itself in react?

What I'm Trying to do: I have the following react component, which is chat box, which toggles to view on clicking a button. The chatBox will be opened or closed according the value in the state 'open'. When the user clicks on the chat button, the function 'toggleChat' is run, which just toggles the value of 'open' state between true and false.
Problem: Now the problem is, when a new message is received, I am trying to keep the count of unread messages, if the chatBox isn't 'opened'. But it fails. In my opinion it should work, but the 'open' state is not what I expect it to be sometimes. Sometimes, even though the chatBox is opened, the open state inside is 'false'.
Minified Code
export default function Chat (props) {
const [open, setOpen] = useState(false);
const [unreadMsgs, setUnreadMsgs] = useState(0);
useEffect(() => {
socket.emit('join', uuid);
socket.on('newMsg', msg => {
setMsgArr(prevMsgArr => [ ...prevMsgArr, { type: 'received', msg }]);
if(!open) setUnreadMsgs(prevCount => prevCount + 1);
});
}, []);
const toggleChat = () => {
if(open) setOpen(false);
else {
setOpen(true);
setUnreadMsgs(0);
}
}
Entire code
import { io } from 'socket.io-client';
import React, {useState, useRef, useEffect} from 'react';
import Menu from '#material-ui/core/Menu';
import MailIcon from '#material-ui/icons/Mail';
import CloseIcon from '#material-ui/icons/Close';
import IconButton from '#material-ui/core/IconButton';
import { makeStyles } from '#material-ui/core/styles';
import Badge from '#material-ui/core/Badge';
import Tooltip from '#material-ui/core/Tooltip';
import FiberManualRecordIcon from '#material-ui/icons/FiberManualRecord';
const socket = io('http://127.1.1.1:4000');
const useStyles = makeStyles({
paper : {
height : 300,
},
list : {
height : '100%',
boxSizing : 'border-box',
},
chatContainer : {
position : 'relative',
height : '95%',
width : 300,
},
chatBox : {
height : '82%',
position : 'absolute',
top : '8%',
width : '100%',
overflowY : 'auto',
},
msgForm : {
width : '100%',
padding : 10,
position : 'absolute',
bottom : 0,
height : '6%',
textAlign : 'center',
},
anchor : {
top : 7,
},
badge : {
background : '#007eff',
},
});
export default function Chat (props) {
const uuid = props.uuid;
const classes = useStyles();
const [open, setOpen] = useState(false);
const [activeStatus, setActiveStatus] = useState('Offline');
const [msgArr, setMsgArr] = useState([]);
const chatBtnRef = useRef();
const chatBoxRef = useRef();
const msgInputRef = useRef();
//working on showing count of unread messages
const [unreadMsgs, setUnreadMsgs] = useState(0);
useEffect(() => {
socket.emit('join', uuid);
socket.on('newMsg', msg => {
setMsgArr(prevMsgArr => [ ...prevMsgArr, { type: 'received', msg }]);
setTimeout(() => {
chatBoxRef.current.scrollTop = chatBoxRef.current.scrollHeight;
}, 50);
console.log(open);
if(!open) setUnreadMsgs(prevCount => prevCount + 1);
});
if(props.isReceiver) {
socket.emit('isReceiverOnline', uuid);
socket.on('isSenderOnline', () => {
setActiveStatus('Online');
});
} else {
socket.emit('isSenderOnline', uuid);
socket.on('isReceiverOnline', () => {
setActiveStatus('Online');
socket.emit('isSenderOnline', uuid);
});
}
socket.on("isOffline", () => {
setActiveStatus('Offline');
});
return () => {
socket.off('isOffline');
socket.off('newMsg');
socket.off('isOnline');
}
}, []);
const handleMsgSend = e => {
e.preventDefault();
let msg = msgInputRef.current.value;
setMsgArr([ ...msgArr, { type: 'sent', msg }]);
e.currentTarget.reset();
socket.emit('newMsg', {uuid, msg});
setTimeout(() => chatBoxRef.current.scrollTop = chatBoxRef.current.scrollHeight, 50);
}
const toggleChat = () => {
if(open) setOpen(false);
else {
setOpen(true);
setUnreadMsgs(0);
}
}
return (
<>
<Tooltip title={ `${activeStatus}` }>
<IconButton>
<FiberManualRecordIcon style={{ height : 14, width : 14, fill : (activeStatus == "Offline")? '#00000057' : 'rgb(136 210 130)'}}/>
</IconButton>
</Tooltip>
<Tooltip title={ `${unreadMsgs} unread messages` }>
<IconButton ref={chatBtnRef} onClick={ toggleChat }>
<MailIcon style={{ fill:'white' }}/>
</IconButton>
</Tooltip>
<Menu
classes={{ paper : classes.paper, list : classes.list}}
anchorEl={chatBtnRef.current}
keepMounted
open={ open }
>
<div style={{ position : 'relative', zIndex : '1', height : '5%', textAlign:'right'}}>
<IconButton onClick={ toggleChat }>
<CloseIcon />
</IconButton>
</div>
<div className={classes.chatContainer}>
<div ref={ chatBoxRef } className={ classes.chatBox }>
{
msgArr.map((msgObj, index) => {
return (
<div key={index} className={`msg-container ${(msgObj.type == 'sent')? 'myMsg' : 'hisMsg'}`}>
<span className='msg'>
{ msgObj.msg }
</span>
</div>
)
})
}
</div>
<form className={ classes.msgForm } onSubmit={ handleMsgSend }>
<input
style ={{
padding : 3,
fontSize : 14,
borderRadius : 3,
width : 250
}}
ref={msgInputRef} type="text"
className={classes.msgInput}
placeholder="Type your Msg here."/>
</form>
</div>
</Menu>
</>
);
}
Try adding the dependencies to useEffect that are used in the function.
Also add const [msgArr, setMsgArr] = useState([]); if it is not there.
useEffect(() => {
socket.emit('join', uuid);
}, []); // you can pass socket here as it's not going to change
useEffect(() => {
socket.on("newMsg", (msg) => {
setMsgArr((prevMsgArr) => [...prevMsgArr, { type: "received", msg }]);
if (!open) setUnreadMsgs((prevCount) => prevCount + 1);
});
return () => socket.removeAllListeners("newMsg"); // remove earlier registered handlers
}, [open]);
Updated the De-registration of the event handler if a dependency changes. (based on comment discussion and answer https://stackoverflow.com/a/67073527/8915198)
Already given the explanation for closures in the comments but the working solution for you should look something like below :-
useEffect(() => {
socket.emit('join', uuid);
}, []);
useEffect(() => {
function getNewMsg(msg){
setMsgArr((prevMsgArr) => [...prevMsgArr, { type: "received", msg }]);
if (!open) setUnreadMsgs((prevCount) => prevCount + 1);
}
socket.on("newMsg",getNewMsg)
return ()=>socket.removeListener("newMsg",getNewMsg);
}, [open]);
Basically the cleanup step I think is necessary as you do with major event paradigms.

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.

create timer with react native using es6

I am looking to add a timer to my app which is built using react native.
I have looked at the link to the timer mixin in the documentation however I have built the rest of the app using es6 so this won't be compatible.
I have tried the below.
In my Main class I have a function called getTimerCountDown
getTimerCountDown() {
setTimeout(() => {
this.setTimeRemaining(this.getTimeRem()-1);
}, 1000);
}
getTimeRem() {
return this.state.timeRemaining;
}
I have tried calling this in componentDidUpdate as shown below. This works as I want it to if I don't make any other interactions with the UI.
If I do (eg I have a button I can click on the view.) as `componentDidUpdate gets called again the conunter gets really quick (as it is getting called x number of times)
componentDidUpdate(){
this.getTimerCountDown();
}
I am not sure if I am completly on the wrong track here or a small change to what I have done can get me what I want.
What is the best way to get a countdown timer working in react native using es6?
Timer Class
on main page
<Timer timeRem={this.getTimeRem()} />
returns
render(){
return (
<View style={styles.container}>
<Text> This is the Timer : {this.props.setTimer} - {this.props.timeRem} </Text>
</View>
)
}
I'm not really sure how that would work even without any other UI interactions. componentDidUpdate is called every time the component is re-rendered, something that happens when the internal state or passed down props have changed. Not something you can count on to happen exactly every second.
How about moving the getTimerCountDown to your componentDidMount method (which is only called once), and then using setInterval instead of setTimeout to make sure the counter is decremented continuously?
Kinda late, but you can try out this component I made for dealing with timers and es6 components in react-native:
https://github.com/fractaltech/react-native-timer
Idea is simple, maintaining and clearing timer variables on the components is a pain, so simply, maintain them in a separate module. Example:
// not using ES6 modules as babel has broken interop with commonjs for defaults
const timer = require('react-native-timer');
// timers maintained in the Map timer.timeouts
timer.setTimeout(name, fn, interval);
timer.clearTimeout(name);
// timers maintained in the Map timer.intervals
timer.setInterval(name, fn, interval);
timer.clearInterval(name);
// timers maintained in the Map timer.immediates
timer.setImmediate(name, fn);
timer.clearImmediate(name);
// timers maintained in the Map timer.animationFrames
timer.requestAnimationFrame(name, fn);
timer.cancelAnimationFrame(name);
Try this
Timer.js
import React, { Component } from "react";
import { View,Text,Button,StyleSheet } from "react-native";
const timer = () => {};
class Timer extends Component {
constructor(props) {
super(props);
this.state = {
remainingTime: 10
};
}
countdownTimer(){
this.setState({remainingTime:10 });
clearInterval(timer);
timer = setInterval(() =>{
if(!this.state.remainingTime){
clearInterval(timer);
return false;
}
this.setState(prevState =>{
return {remainingTime: prevState.remainingTime - 1}});
},1000);
}
render() {
return (
<View style={styles.container}>
<Text>Remaining time :{this.state.remainingTime}</Text>
<Button title ="Start timer" onPress={()=>this.countdownTimer()}/>
</View>
);
}
}
const styles = StyleSheet.create({
container:{
flex:1,
justifyContent:'center',
alignItems:'center',
}
});
export default Timer;
App.js
import React, { Component } from "react";
import { View,Text,Button,StyleSheet } from "react-native";
import Timer from './timer';
export default class App extends Component{
render(
return (<Timer />)
);
}
Here is full code how you can create a timer (pomodoro Timer) in react-native;
Timer.js
import React from 'react'
import {Vibration, View, Button, Text, TextInput, StyleSheet} from 'react-native'
let pomInterval;
export default class Timer extends React.Component {
constructor() {
super();
this.state = {
minutes: 5,
seconds: 0,
workmins: 5,
worksecs: 0,
breakMins: 2,
breakSecs: 0,
timerState: 'WORK TIMER',
btnState: 'Start'
}
}
vibrate = () => {
Vibration.vibrate([500, 500, 500])
}
pomTimer = () => {
pomInterval = setInterval(() => {
let newSec = this.state.seconds;
newSec--;
if(newSec < 0) {
newSec = 59;
this.state.minutes--;
}
this.setState({
seconds: newSec,
})
if(newSec <= 0 && this.state.minutes <= 0) {
this.vibrate();
if(this.state.timerState == 'WORK TIMER') {
this.setState({
timerState: 'BREAK TIMER',
minutes: this.state.breakMins,
seconds: this.state.breakSecs
})
}else {
this.setState({
timerState: 'WORK TIMER',
minutes: this.state.workmins,
seconds: this.state.worksecs
})
}
}
}, 1000);
}
changeWorkMin = mins => {
clearInterval(pomInterval);
this.setState({
minutes: mins || 0,
workmins: mins || 0,
btnState: 'Start'
})
}
changeWorkSec = secs => {
clearInterval(pomInterval);
this.setState({
seconds: secs || 0,
worksecs: secs || 0,
btnState: 'Start'
})
}
changeBreakMin = mins => {
clearInterval(pomInterval);
this.setState({
breakMins: mins || 0,
btnState: 'Start'
})
}
changeBreakSec = secs => {
clearInterval(pomInterval);
this.setState({
breakSecs: secs || 0,
btnState: 'Start'
})
}
// Creating the functionality for the pause/start button
chnageBtnState = () => {
if(this.state.btnState == 'Start') {
this.pomTimer();
this.setState({
btnState: 'Pause'
})
}else {
clearInterval(pomInterval);
this.setState({
btnState: 'Start'
})
}
}
// Creating the functionality for the reset button
reset = () => {
clearInterval(pomInterval);
if(this.state.timerState == 'WORK TIMER') {
this.setState({
minutes: this.state.workmins,
seconds: this.state.worksecs,
btnState: 'Start'
})
}else {
this.setState({
minutes: this.state.breakMins,
seconds: this.state.breakSecs,
btnState: 'Start'
})
}
}
render() {
return (
<View style={styles.viewStyles}>
<Text style={styles.textStyles}>{this.state.timerState}</Text>
<Text style={styles.textStyles}>{this.state.minutes}:{this.state.seconds}</Text>
<Text>
<Button title={this.state.btnState} onPress={this.chnageBtnState} />
<Button title='Reset' onPress={this.reset} />
</Text>
<Text>Work Time:</Text>
<TextInput style={styles.inputStyles} value={this.state.workmins.toString()} placeholder='Work Minutes' onChangeText={this.changeWorkMin} keyboardType='numeric' />
<TextInput style={styles.inputStyles} value={this.state.worksecs.toString()} placeholder='Work Seconds' onChangeText={this.changeWorkSec} keyboardType='numeric' />
<Text>Break Time:</Text>
<TextInput style={styles.inputStyles} value={this.state.breakMins.toString()} placeholder='Break Minutes' onChangeText={this.changeBreakMin} keyboardType='numeric' />
<TextInput style={styles.inputStyles} value={this.state.breakSecs.toString()} placeholder='Break Seconds' onChangeText={this.changeBreakSec} keyboardType='numeric' />
</View>
)
}
}
// Creating a style sheet to write some styles
const styles = StyleSheet.create({
viewStyles: {
alignItems: 'center'
},
textStyles: {
fontSize: 48
},
inputStyles: {
paddingHorizontal: 50,
borderColor: 'black',
borderWidth: 1
}
})
App.js
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import Timer from './timer';
export default function App() {
return (
<View style={styles.container}>
<Timer />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
This is how we can create a pomodoro timer, a timer which has both WORK TIMER and BREAK TIMER and it vibrates the phone as one of the timer reaches its end. I also added the input functionality i.e, you can dynamically change the value of the minutes and seconds (whether work timer or break timer is going on). I also added a start/pause button and a reset button.

Categories