react native setState in setInterval(), then setInterval executes error - javascript

Here is my code
componentDidMount() {
let that = this;
setInterval(() => {
that.setState({number: 1});
}, 2000);
}
I have writen 'let that = this;', but it's also error. In 2 seconds it executes more than once.

Why aren't you using this itself in the setInterval? You have used the fat arrow function, so you still can use this inside.
Here's a sample code:
constructor (props) {
super(props)
this.state = {
number: 0
}
}
componentDidMount(){
setInterval(() => {
this.setState({number: parseInt(this.state.number, 10) + 1 });
}, 2000);
}
render() {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center',}}>
<Text>
{this.state.number }
</Text>
</View>
);
}
Expo Demo

Related

throttle function does not work when i update state inside this function

below is code. runonce function does nt work as (it should run only once in 3000ms)
but if i does not update state then it works fine.
const HomeScreen = ({ navigation }) => {
const [count, setCount] = useState(1)
function runOnce(func, delay) {
let timer = null;
return function (...args) {
if (!timer) {
timer = setTimeout(() => {
timer = null;
}, delay);
return func(...args);
}
};
}
const handleButtonClick = runOnce(() => {
console.log("This function will run only once in 3000 milliseconds");
//here if i don't update below state then console runs only once in 3000ms
// but if i update state then it runs whenever i click button
setCount(prev => prev + 1)
}, 3000);
return <SafeAreaView style={{ flex: 1 }}>
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text style={{ margin: 10, color: 'red', fontSize: 24 }}>{count}</Text>
<TouchableOpacity style={{ marginTop: 10 }} onPress={() => handleButtonClick()}>
<Text style={{ color: 'blue' }}>Increment Count</Text>
</TouchableOpacity>
</View>
</SafeAreaView>
}
as expected the function inside runonce should run only once in every 3000ms no mattar how many times you click
When updating state, your function reruns and reconstructs your click handler to be a new function on every render, each time with a new timer.
To avoid this, memoize the function using useCallback.
const handleButtonClick = useCallback(() => runOnce(() => {
console.log("This function will run only once in 3000 milliseconds");
//here if i don't update below state then console runs only once in 3000ms
// but if i update state then it runs whenever i click button
setCount(prev => prev + 1)
}, 3000), []);
I can see that you have created your own function for doing an interval breakout or delay to run a function. But Javascript already helps us to do this with the help of these two functions.
setInterval() - To run a function with a given interval in ms. (https://www.w3schools.com/jsref/met_win_setinterval.asp)
setTimeout() - To run a function after a certain time delay in ms.
(https://www.w3schools.com/jsref/met_win_settimeout.asp)
I hope this helps you to reduce the redundant code you have.

Restart Countdown timer in react native

working on a countdown timer, I want to be able to restart the countdown onpress of the button, however, I don't think the button is functional as I get no feedback. can someone please point me in the right direction. below is a trimmed down sample code of what I am trying to achieve.
export default class Example extends Component {
constructor(props) {
super(props);
this.state = {
timer: 10,
timesup: false,
timing: true,
showWelcome: true,
};
}
componentDidMount() {
this.clockCall = setInterval(() => {
this.decrementClock();
}, 1000);
}
startTimer = () => {
this.setState({
timing: true,
timer: 30,
showWelcome: false
})
}
decrementClock = () => {
this.setState((prevstate) => ({
timer: prevstate.timer - 1
}), () => {
if (this.state.timer === 0) {
clearInterval(this.clockCall)
this.setState({
timesup: true,
timing: false,
showWelcome: false,
})
}
})
}
componentWillUnmount() {
clearInterval(this.clockCall);
}
render() {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
{this.state.timesup && (
<Text style={{fontSize: 18, color: '#000'}}>
Time up
</Text>)}
{this.state.timing && (
<Text style={{fontSize: 18, color: '#000'}}>
{this.state.timer}
</Text>)}
{this.state.showWelcome && (
<Text style={{ fontSize: 20 }}>Welcome</Text>
)}
<Button Onpress={this.startTimer.bind(this)} title='play' />
</View>
)
}
}
I believe you're looking for onPress, not Onpress. Also, if you are using:
startTimer = () => ...
Using:
this.startTimer.bind
Has no effect, since the method is already bound to this by the arrow function. You can then simply use:
onPress={this.startTimer}

Why is socket.on() from a previous screen component executed?

I have two react components. the first Lobby uses react-native-navigation to push Gameroom to the stack. It passes props such as the socket object and other data to the Gameroom component
when the back button of the navigation bar is pressed inside Gameroom, a socket.io leave event is emitted, and I have verified it is heard by the server, so the socket passed through props works. the server then emits an event left back to the socket.io room (Gameroom component).
the left event listener, if placed inside Gameroom's componentDidMount() does not execute. However, if the same socket.io event listener is placed in Lobby component (previous screen) componentDidMount() the event is heard
I've tried adding the event listener to multiple componentDidMount functions, I also thought about using the Context API, but I'm not working with nested components. I'm passing the socket object in react-native-navigation's {passProps} from screen to screen
Lobby:
imports ...
const socket = io("http://192.xxx.xxx.xx:3000");
export default class Lobby extends React.Component {
static options(passProps) {
return {
topBar: {
background: {
color: "transparent"
},
drawBehind: true,
visible: true,
animate: true,
leftButtons: [
{
id: "leave",
icon: require("../assets/img/Chevron.png")
}
]
}
};
}
constructor(props) {
super(props);
this.state = {
username: "Initializing...",
queue: []
};
}
componentDidMount() {
Navigation.events().bindComponent(this);
socket.emit("lobbyEntry");
socket.on("lobbyEntry", entry => {
this.setState({ queue: entry.lobby, username: socket.id });
});
socket.on("userJoined", lobby => {
this.setState({ queue: lobby });
});
// socket.on("left", () => {
// alert("Opponent Left...Oh well");
// Navigation.pop(this.props.componentId);
// });
}
navigationButtonPressed({ buttonId }) {
switch (buttonId) {
case "leave":
socket.emit("leave");
Navigation.popToRoot(this.props.componentId);
break;
}
}
createMatch = () => {
if (this.state.username != "Initializing...") {
socket.emit("findMatch");
socket.on("alreadyCreated", () => {
alert("You already created a match!");
});
socket.on("listUsers", lobby => {
this.setState({ queue: lobby });
});
socket.on("matchFound", data => {
Navigation.push(this.props.componentId, {
component: {
name: "Gameroom",
passProps: {
room: data.id,
socket: socket,
firstMove: data.firstMove,
p1: data.p1,
p2: data.p2
}
}
});
});
} else {
alert("Wait for Username to be initialized...");
}
};
render() {
const bg = getBackground();
return (
<ImageBackground source={bg} style={{ height: "100%", width: "100%" }}>
<View style={styles.title_container}>
<Text style={styles.title_sm}>Matchmaking Lobby</Text>
</View>
<View style={styles.alt_text_container}>
<Text style={styles.alt_text_md}>Username:</Text>
<Text style={styles.alt_text_md}>{this.state.username}</Text>
</View>
<View
style={{
flexDirection: "column",
justifyContent: "center",
alignItems: "center"
}}
>
<XplatformButton onPress={this.createMatch} text={"Create a Match"} />
</View>
<View style={styles.alt_text_container}>
<Text style={styles.alt_text_sm}>Players actively searching...</Text>
<FlatList
style={styles.alt_text_container}
data={this.state.queue}
renderItem={({ item, index }) => (
<Text style={styles.alt_text_md} key={index}>
{item}
</Text>
)}
/>
</View>
</ImageBackground>
);
}
}
Gameroom:
import ...
export default class Gameroom extends React.Component {
static options(passProps) {
return {
topBar: {
title: {
fontFamily: "BungeeInline-Regular",
fontSize: styles.$navbarFont,
text: "Gameroom - " + passProps.room,
color: "#333"
},
background: {
color: "transparent"
},
drawBehind: true,
visible: true,
animate: true,
leftButtons: [
{
id: "leave",
icon: require("../assets/img/Chevron.png")
}
]
}
};
}
constructor(props) {
super(props);
Navigation.events().bindComponent(this);
}
navigationButtonPressed({ buttonId }) {
switch (buttonId) {
case "leave":
this.props.socket.emit("leave");
Navigation.pop(this.props.componentId);
break;
}
}
componentDidMount() {
// socket.on("left", () => {
// alert("Opponent Left...Oh well");
// Navigation.pop(this.props.componentId);
// });
}
render() {
const bg = getBackground();
return this.props.p2 != null ? (
<Gameboard
room={this.props.room}
you={
this.props.socket.id == this.props.p1.username
? this.props.p1.marker
: this.props.p2.marker
}
opponent={
this.props.socket.id != this.props.p1.username
? this.props.p2.marker
: this.props.p1.marker
}
move={this.props.firstMove}
socket={this.props.socket}
/>
) : (
<ImageBackground style={styles.container} source={bg}>
<View style={{ marginTop: 75 }}>
<Text style={styles.alt_text_md}>
Waiting for Opponent to Join...
</Text>
</View>
</ImageBackground>
);
}
}
I expect the event listener to execute from the current screen's componentDidMount() function, but it only executes if it's inside the previous screen's componentDidMount()
When you create a component,
the constructor -> componentWillMount -> render -> componentDidMount is
followed.
In your Lobby class, the event listener is run because it is in ComponentDidmont.
However, the event listener of the Gameroom class is inside the constructor. If executed within the constructor, the event cannot be heard because it is not yet rendered.
Event listeners are called when they appear on the screen
Usage
componentDidMount() {
this.navigationEventListener = Navigation.events().bindComponent(this);
}
componentWillUnmount() {
// Not mandatory
if (this.navigationEventListener) {
this.navigationEventListener.remove();
}
}

How do i loop animated-circular-progress component

I'm using the react-native-circular-progress component and I'm trying to loop an animation every 30 seconds.
i.e the animation is 30 seconds long and i want it to restart as soon as its done
The component exposes a function named reAnimate which I have put in a setInterval function as soon as the component mounts.
import React from 'react';
import { StyleSheet, Text, View,Dimensions, Easing } from 'react-native';
import { AnimatedCircularProgress } from 'react-native-circular-progress';
const MAX_POINTS = 30;
export default class App extends React.Component {
state = {
isMoving: false,
pointsDelta: 0,
points: 1,
};
componentDidMount(){
setInterval(
() => this.circularProgress.reAnimate(0,100, 30000,Easing.linear),
30000
);
}
render() {
const { width } = Dimensions.get("window");
const window = width - 120;
const fill = (this.state.points / MAX_POINTS) * 100;
return (
<View style={styles.container} >
<AnimatedCircularProgress
size={window}
width={7}
backgroundWidth={5}
fill={0}
tintColor="#ef9837"
backgroundColor="#3d5875"
ref={(ref) => this.circularProgress = ref}
arcSweepAngle={240}
rotation={240}
lineCap="round"
>
{fill => <Text style={styles.points}>{Math.round((MAX_POINTS * fill) / 100)}</Text>}
</AnimatedCircularProgress>
</View>
);
}
}
const styles = StyleSheet.create({
points: {
textAlign: 'center',
color: '#ef9837',
fontSize: 50,
fontWeight: '100',
},
container: {
flex: 1,
justifyContent: 'space-between',
alignItems: 'center',
backgroundColor: '#0d131d',
padding: 50,
},
pointsDelta: {
color: '#4c6479',
fontSize: 50,
fontWeight: '100',
},
pointsDeltaActive: {
color: '#fff',
},
});
This is working... BUT... the animation only starts 30s after the component mounts. How do i get it to loop immediately?
Any help will be greatly appreciated.
Thank you.
The reason is setInterval will not fire immediately it will start after the duration you passed i.e 30 mins, So all you have to do is make a call initially before setting the interval, also don't forget to clear the interval.
Here's how I would it:
componentDidMount(){
this.circularProgress.animate(100, 30000,Easing.linear);
this.intervalId = setInterval(
() => this.circularProgress.reAnimate(0,100, 30000,Easing.linear),
30000
);
}
componentWillUnmount() {
clearInterval(this.intervalId);
}

Calling a method on a particular instance of a React component from another component

I'm trying to make a simple Pomodoro timer. I need to make the pause and stop buttons work.
I defined a separate component called 'Timer' and added two buttons: 'Pause' and 'Stop' which obviously have to affect the state of the Timer.
How do I call the stop and pause method of Timer when the respective Buttons are pressed?
I understand that I can do this by simply including the buttons within the Timer class but I want to learn how to achieve something similar in the future and I'd like to keep the counter part of the Timer independent.
Here is the code:
import React from 'react'
import { Text, View, Button, StyleSheet } from 'react-native';
import { Constants } from 'expo';
class Timer extends React.Component{
constructor (props){
super(props)
this.state = {
minutes: props.minutes,
seconds: props.seconds,
count: 0,
}
}
dec = () => {
this.setState(previousState => {
if (previousState.seconds==0){
return {
minutes: previousState.minutes-1,
seconds: 59,
count: previousState.count+1,
}
}
else{
return{
minutes: previousState.minutes,
seconds: previousState.seconds-1,
count: previousState.count+1,
}
}
});
}
componentDidMount (){
setInterval(this.dec,1000);
}
render (){
return(
<View style = {{flexDirection: 'row'}}>
<Text style = {styles.timerCount}> {this.state.minutes}</Text>
<Text style = {styles.timerCount}>:</Text>
<Text style = {styles.timerCount}> {this.state.seconds} </Text>
</View>
)
}
stop (){
console.log('stop')
}
pause (){
console.log('pause')
}
}
export default class App extends React.Component {
stop (){
console.log('stop')
}
render() {
return(
<View style={styles.container}>
<Timer style = {styles.timer} minutes={25} seconds = {0}/>
<View style = {styles.buttonContainer}>
<Button title = 'Pause' style = {styles.timerButton} color = 'white' onPress = {()=>{console.log("call the timer's pause method here")}}/>
<Button title = 'Stop' style = {styles.timerButton} color = 'white' onPress = {()=>{console.log("call the timer's stop method here")}}/>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
buttonContainer: {
flexDirection: 'row',
},
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
paddingTop: 50,
backgroundColor: '#EC3D40',
},
timer: {
backgroundColor: '#EC3D40',
paddingTop: 50,
},
timerCount: {
fontSize: 48,
color: 'white',
backgroundColor: '#EC3D40',
paddingTop: 10,
paddingBottom: 10,
},
timerButton:{
borderColor: 'white',
backgroundColor: 'transparent',
}
});
Well actually you can do that using ref feature.
You can create a ref and assign it to your timer component. Then you can call this component's methods.
export default class App extends React.Component {
timerRef = React.createRef();
render() {
return(
<View style={styles.container}>
<Timer style = {styles.timer} minutes={25} seconds = {0} ref={this.timerRef}/>
<View style = {styles.buttonContainer}>
<Button title = 'Pause' style = {styles.timerButton} color = 'white' onPress = {()=>{console.log("call the timer's pause method here"); this.timerRef.current.pause();}}/>
<Button title = 'Stop' style = {styles.timerButton} color = 'white' onPress = {()=>{console.log("call the timer's stop method here"); this.timerRef.current.stop();}}/>
</View>
</View>
);
}
}
But this doesn't seem like a react way of doing things.

Categories

Resources