I want to clear the interval when stop button is clicked it seems the clearInterval is not working there as expected. The timer doesn't stop when handleStop function is triggered or when handleReset id triggered
Here's what I am trying to do:
import React from "react"
import useApp from "../App"
import Interact from "./Interact";
const Timer = () => {
let timer;
const {millisec , setMillisec , sec , setSec , min , setMin , hr , setHr} = useApp();
const handleStart = () => {
timer = setInterval(() => {
setMillisec((prev) => {
if (prev === 100) {
setSec((prevB) =>{
if(prevB === 60){
setMin(prevC => {
if(prevC === 60){
setHr(prevD => prevD + 1)
return 0
}
return prevC + 1
})
return 0
}
return prevB + 1
});
return 0;
}
return prev + 1;
});
}, 10);
};
const handleStop = () => {
clearInterval(timer)
}
const handleReset = () => {
clearInterval(timer)
setMillisec(0)
setSec(0)
setMin(0)
setHr(0)
}
return(
<>
<div className="parent">
<div className="main">
<h1 className="heading">Stop Watch</h1>
<div className="timer">
<div className ="hour">{hr}</div>
<div className ="min">{min}</div>
<div className ="sec">{sec}</div>
<div className ="millisec">{millisec}</div>
</div>
<Interact handleStart = {handleStart}
handleStop = {handleStop}
handleReset ={handleReset}
/>
</div>
</div>
</>
)
}
export default Timer
variable timer re-declare every time when Timer component rendered. Thus, the setInterval reference is reset.
You can declare timer at global scope, like
...
import Interact from "./Interact";
let timer;
const Timer = () => {
const {millisec , setMillisec , sec , setSec , min , setMin , hr , setHr} = useApp();
const handleStart = () => {
...
or can also use react-hooks to maintain setInterwal reference. like this
Pass your setInterval() to a clearInterval() when you need to break it.
Related
When I click on the button, if the value of the button is 0, then the text will not show in that space. I mean, how can it be done without showing -10?
console.log(variable)
The this is the error I'm getting ..
const Mobile = () => {
const [charge, setCharge] = useState(20)
const setMobileCharge = () => setCharge(charge - 10)
if (setMobileCharge === 0) {
return 0;
}
return (
<div>
<h3>Charge : {charge}</h3>
<button onClick={setMobileCharge}>Battery down</button>
</div>
);
};
export default Mobile;
this happens because the react "ticks" your render is not fully synced with the state ... so to prevent that you can set the new state like that
setCharge(oldCharge => oldCharge -10 )
you can add a condition like below,
const Mobile = () => {
const [charge, setCharge] = useState(20)
const setMobileCharge = () => {
if(charge > 0) setCharge(charge - 10)} //here
if (setMobileCharge === 0) {
return 0;
}
return (
<div>
<h3>Charge : {charge === 0 ? "" : charge}</h3> //here
<button onClick={setMobileCharge}>Battery down</button>
</div>
);
};
I'm don't quite understand your question.
But if you want when 0 not to decrease, you can :
const setMobileCharge = () => {
if (charge > 0) {
setCharge(charge - 10)
}
}
If you want when 0 Mobile component disappear you can:
if (charge === 0) {
return 0;
}
I'm implementing an input number using React. This input number has a + and - button that increases or decreases the value inside an input.
The onClick methods works fine, however, I wonder, what's the method to keep triggering the increase or decrease methods while the button keeps pressed.
Here are my methods to update the state:
const increaseValue = () => {
const val = isNaN(tempValue) || tempValue === '' ? 0 : Number(tempValue)
setTempValue(val + 1)
}
const decreaseValue = () => {
const val = isNaN(tempValue) || tempValue === '' ? 0 : Number(tempValue)
setTempValue(val - 1)
}
and here's the component
<div className="flex flex-row h-8 w-20 rounded-lg relative bg-transparent">
<button
className=" bg-action text-action-100 hover:text-gray-300 hover:bg-action-300 h-full w-20 rounded-l cursor-pointer outline-none active:bg-action-200"
onClick={decreaseValue}
>
<span className="m-auto text-md font-thin">−</span>
</button>
<input
className="outline-none focus:outline-none text-center w-full bg-action font-thin text-xs antialiased flex items-center text-white"
name="custom-input-number"
value={tempValue}
onChange={e => {
setTempValue(e.target.value)
type === 'number' && setIsEditing(true)
}}
></input>
<button
className="bg-action text-action-100 hover:text-gray-300 hover:bg-action-300 h-full w-20 rounded-r cursor-pointer active:bg-action-200"
onClick={increaseValue}
>
<span className="m-auto text-md font-thin">+</span>
</button>
</div>
Now, I want to add the feature to keep increasing/decreasing while the button is still pressed.
The simplest way to do this is probably to keep track of when a button is first pressed and then when it is released, and using a useRef to keep track of a time interval for how often the button action should trigger. That will look like this:
const intervalRef = useRef(null);
Then we need a function to handle when we begin pressing the plus button:
const startCountUp = () => {
if (intervalRef.current) return;
intervalRef.current = setInterval(() => {
setTempValue((prevCounter) => prevCounter + 1);
}, 10);
};
We'll add a similar function for when the user presses the minus button, which counts down:
const startCountDown = () => {
if (intervalRef.current) return;
intervalRef.current = setInterval(() => {
setTempValue((prevCounter) => prevCounter - 1);
}, 10);
};
Next, we need a way to stop incrementing the counter when we release the button. We'll create a function to handle stopping the timer:
const stopCounter = () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
};
Then we need to trigger our functions properly when the user interacts with a button. Our button's onMouseDown should call startCountUp (for the + button), and when the user releases the mouse we want to use onMouseUp to call stopCounter so we stop incrementing the counter. Also, we want to be prepared for the case where the user moves their mouse outside of the button, so we use onMouseLeave to also call stopCounter.
All put together, here's what that looks like:
const [tempValue, setTempValue] = useState(0);
const intervalRef = useRef(null);
const startCountUp = () => {
if (intervalRef.current) return;
intervalRef.current = setInterval(() => {
setTempValue((prevCounter) => prevCounter + 1);
}, 10);
};
const startCountDown = () => {
if (intervalRef.current) return;
intervalRef.current = setInterval(() => {
setTempValue((prevCounter) => prevCounter - 1);
}, 10);
};
const stopCounter = () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
};
useEffect(() => {
return () => stopCounter(); // when App is unmounted we should stop counter
}, []);
return (
<div className="App">
<div>
<button
onMouseDown={startCountDown}
onMouseUp={stopCounter}
onMouseLeave={stopCounter}
>
<span>−</span>
</button>
<input
name="custom-input-number"
value={tempValue}
onChange={(e) => {
setTempValue(e.target.value);
}}
></input>
<button
onMouseDown={startCountUp}
onMouseUp={stopCounter}
onMouseLeave={stopCounter}
>
<span>+</span>
</button>
</div>
</div>
);
And here's a full example of that on CodeSandbox:
UPDATE: I have figured out a muuuuch simpler workaround by sing in the typescript file so the JS parent is no longer needed. ~facepalm~ Thanks for all your suggestions!
I am trying to get a button to trigger the function affTimer() inside the child function component but I keep getting the error "this is undefined" in relation to the function call. Here is the two code files:
affType.js
import React, {Component} from 'react';
import ReactPlayer from 'react-player'
import { Link } from 'react-router-dom';
import affirmationService from '../Services/requestService'
import affTrack from '../audio/inner.wav';
import warn from '../audio/warning.wav';
import Player from '../Player/Player';
import videoBG from '../videos/InnerStrength.mp4';
import Type from '../Type/Type.tsx';
import Button from "../customButton";
import {tXP} from '../Type/Type.tsx';
class affType extends Component {
constructor(props) {
super(props);
this.state = {character: undefined};
this.child = React.forwardRef();
this.startGame = this.startGame.bind(this);
}
async componentDidMount() {
const { match: { params } } = this.props;
//let affirmation_id = params.affirmation_id;
//let response = await affirmationService.getById(affirmation_id);
//this.setState({character: response.data});
setTimeout(() => {
document.getElementById('overlay_blk_fast').style.opacity = 0;
setTimeout(() => {
document.getElementById('overlay_blk_fast').style.display = 'none';
}, 1000);
}, 10);
}
spawnDialog() {
document.getElementById('overlay_1').style.display = 'block';
setTimeout(() => {
document.getElementById('overlay_1').style.opacity = 1;
}, 10);
}
destroyDialog() {
document.getElementById('overlay_1').style.opacity = 0;
setTimeout(() => {
document.getElementById('overlay_1').style.display = 'none';
}, 1000);
}
repeat() {
document.getElementById('overlay_2').style.opacity = 0;
document.querySelector('video').play();
setTimeout(() => {
document.getElementById('overlay_2').style.display = 'none';
}, 1000);
}
test_ended() {
document.getElementById('overlay_2').style.display = 'block';
setTimeout(() => {
document.getElementById('audio_end').play();
document.getElementById('overlay_2').style.opacity = 1;
}, 10);
}
startGame() {
var track = document.getElementById('aff');
track.play();
this.child.current.affTimer();
}
render() {
return (
<div>
<div className="contentplayer">
<audio id='aff'><source src={affTrack} /></audio>
<video autoPlay muted loop id="myVideo">
<source src={videoBG} type="video/mp4" />
</video>
<audio id="audio_end" src="/Audio/Inner Strength completed quest - play with completed quest prompt.wav"/>
</div>
<p>{tXP}</p>
<Button
border="none"
color="pink"
height = "200px"
onClick={this.startGame}
radius = "50%"
width = "200px"
children = "Start!"
/>
<Type ref={this.child}>
</Type>
<div className="aligntopright" onClick={() => {this.spawnDialog()}}>
<div className="backbtn-white"></div>
</div>
<div className="overlay_blk_fast" id="overlay_blk_fast"></div>
<div className="overlay" id="overlay_1">
<div className="dialog">
<div className="dialogcontainer">
<img className="dialogbg"/>
<h3 className="dialogtext">Are you sure you would like to go back to the selection page?</h3>
<h2 className="no" onClick={() => {this.destroyDialog()}}>No</h2>
<Link to="/affirmation"><h2 className="yes">Yes</h2></Link>
</div>
</div>
</div>
<div className="overlay" id="overlay_2">
<div className="dialog">
<div className="dialogcontainer">
<img className="dialogbg"/>
<h3 className="dialogtext">Would you like to repeat this quest?</h3>
<Link to="/affirmation"><h2 className="no">Go back</h2></Link>
<h2 className="yes" onClick={() => {this.repeat()}}>Repeat</h2>
</div>
</div>
</div>
</div>
)
}
}
export default affType;
type.tsx
import React, {Component} from 'react';
import useTypingGame from "react-typing-game-hook";
import { textSpanContainsTextSpan } from 'typescript';
var xpM = 0;
var i = 0;
var err = 0;
var xp = 5;
var tXP = 0;
var addXP = 1;
var bonus = 0;
var bonusCounter = 0;
//var warnP = new Audio({warn});
//var affTrackP = new Audio('../audio/inner.wav');
function TypeF() {
let text_array = [
"There is strength and solidity within me",
"Courage is flooding through my veins",
"I possess strength within my heart",
"I am leading the charge with courage, and a vigorous resolution",
"There is a force inside me that is unbelievably powerful",
"There is a brave, radiant spirit inside me",
"I am a tall tree, with thick and strong roots",
"I was born for this",
"There is a divinity within",
"I am a force of nature",
"I possess the mental fortitude of those who climb the highest peaks",
"I was born with a determined spirit",
"There is an intensity in my eyes"
];
let text = text_array[i];
const {
states: {
charsState,
length,
currIndex,
currChar,
correctChar,
errorChar,
phase,
startTime,
endTime
},
actions: { insertTyping, resetTyping, deleteTyping }
} = useTypingGame(text);
const handleKey = (key: any) => {
if (key === "Escape") {
resetTyping();
} else if (key === "Backspace") {
deleteTyping(false);
} else if (key.length === 1) {
insertTyping(key);
}
};
if (currIndex + 1 === length) {
xpM = xpM + 1;
bonusCounter = bonusCounter + 1;
err = err + errorChar;
addXP = ((xp * correctChar) - (err * 2)) * xpM;
if (err > correctChar) {
addXP = correctChar * 3;
}
tXP = tXP + addXP;
if (bonusCounter >= 5) {
bonus = bonus + 1;
bonusCounter = 0;
}
resetTyping();
}
var tmr;
var cd = 18;
function affTimer() {
tmr = setInterval(tock, 1000);
if (i >= text_array.length) {
clearInterval(tmr);
}
}
function tock() {
if (cd > 0) {
cd = cd - 1;
console.log(cd);
}
else if (cd <= 0) {
if (i < text_array.length) {
i = i + 1;
cd = 18;
resetTyping();
}
else {
i = text_array.length;
}
}
}
return (
<div className='container'>
<div
className="typing-test"
id="start"
onKeyDown={(e) => {
handleKey(e.key);
e.preventDefault();
}
}
tabIndex={0}
>
{text.split("").map((char: string, index: number) => {
let state = charsState[index];
let color = state === 0 ? "white" : state === 1 ? "green" : "red";
return (
<span
key={char + index}
style={{ color }}
className={currIndex + 1 === index ? "curr-letter" : ""}
>
{char}
</span>
);
})}
</div>
<h2 className='debug'> TIMER: {cd}, I: {i}, ERRORS: {err}, MULTIPLIER: {xpM}, Type XP: {correctChar * xp}, CurrXP: {correctChar * xp * xpM} XPTotal: {tXP} bonusCounter: {bonusCounter}, BONUS: {bonus}</h2>
</div>
);
}
export {tXP};
export default TypeF;
Any help would be amazing, I have been stuck on this for 2 days and it is the last bit I need to complete so I can move to the next phase.
Your child component is a function component. You can't get a ref to an instance of a function component, because there is no instance. If you really need to use a function component and also expose some custom object as a ref, then you can use the useImperativeHandle hook plus forwardRef to define what the parent component should receive on its ref. For example:
const Type = React.forwardRef((props, ref) => {
// ...
useImperativeHandle(ref, () => {
// The following object is what will get assigned to the
// parent component's this.child.current
return {
afftimer: function () {
tmr = setInterval(tock, 1000);
if (i >= text_array.length) {
clearInterval(tmr);
}
}
}
});
// ...
})
But useImperativeHandle is not a common thing to use. There is likely a more standard way to solve your problem. The normal way for a parent component to tell a child component what to do is with props, not with refs.
This question already has answers here:
Why does calling react setState method not mutate the state immediately?
(9 answers)
Closed 2 years ago.
import React, {useState} from 'react';
function Slides({slides}) {
const [slideCount, setSlideCount] = useState(0);
const [slide, setSlide] = useState(slides[0]);
const [restartDisable, setRestartDisable] = useState(true);
const [previousDisable, setPreviousDisable] = useState(true);
const [nextDisable, setNextDisable] = useState(false);
const restartClick = () => {
setRestartDisable(true);
setPreviousDisable(true);
setNextDisable(false);
setSlide(slides[0]);
setSlideCount(0);
console.log("restartCLick ",slideCount);
}
const previousClick = () => {
setSlideCount(prevCount => prevCount - 1);
if (slideCount === 0) {
setRestartDisable(true);
setPreviousDisable(true);
setNextDisable(false);
setSlide(slides[0]);
} else {
setSlide(slides[slideCount]);
}
console.log("previousCLick ",slideCount);
}
const nextClick = () => {
let newSlideCount = slideCount
newSlideCount++
console.log(newSlideCount)
setSlideCount(newSlideCount);
if (slideCount === (slides.length - 1)) {
setNextDisable(false);
setSlideCount(prevCount => prevCount + 1);
setSlide(slides[slideCount]);
} else {
setRestartDisable(false);
setPreviousDisable(false);
setSlide(slides[slideCount]);
}
console.log("nextCLick ",slideCount);
}
return (
<div>
<div id="navigation" className="text-center">
<button data-testid="button-restart" className="small outlined" disabled={restartDisable} onClick={()=>restartClick()}>Restart</button>
<button data-testid="button-prev" className="small" disabled={previousDisable} onClick={()=>previousClick()}>Prev</button>
<button data-testid="button-next" className="small" disabled={nextDisable} onClick={()=>nextClick()}>Next</button>
</div>
<div id="slide" className="card text-center">
<h1 data-testid="title">{slide.title}</h1>
<p data-testid="text">{slide.text}</p>
</div>
</div>
);
}
export default Slides;
The setSlideCount() is not setting the slideCount as expected, its incrementing late.
Whenever I click nextSlide the increment is shown in the react developer tools but the value remains same of the slideCount. Same thing applies for previousClick button also. But for restart button it works properly in setting to 0 but for the next button clicks and previous button clicks the slideCount value is not updating as expected, please help in setting slideCount value.
That's because setState is an asynchronous operation and won't update the state immidiately.
Here is the changes you need to make in your funtions:
const previousClick = () => {
setSlideCount((prevCount) => {
const newSlideCount = prevCount - 1;
if (newSlideCount === 0) {
setRestartDisable(true);
setPreviousDisable(true);
setNextDisable(false);
setSlide(slides[0]);
} else {
setSlide(slides[newSlideCount]);
}
console.log("previousCLick ", newSlideCount);
return newSlideCount;
});
};
const nextClick = () => {
setSlideCount((prevValue) => {
const newSlideCount = prevValue + 1;
if (newSlideCount === slides.length - 1) {
setNextDisable(false);
setSlideCount((prevCount) => prevCount + 1);
setSlide(slides[newSlideCount]);
} else {
setRestartDisable(false);
setPreviousDisable(false);
setSlide(slides[newSlideCount]);
}
console.log("nextCLick ", newSlideCount);
return newSlideCount;
});
};
I've been trying to create a component similar to instagram stories for a few hours. That is nothing more than an interactive carousel, where you can move forward and backward. The thing is, my strategy with setTimeOut fires every time I interact and does not cancel the state, so if the user is in the first photo and clicks 5x in a row to go to the sixth photo, in a few seconds the settimeout will be like a tsunami and the component advances 5x stories in a row starting from 6. I wanted somehow to reset my timer every time the user clicks on a new image. Seems crazy or is it possible?
I don't know if there is anyone with enough patience to see the code, if not, if someone at least knows a new approach. Thank you very much, I have been trying to resolve this for some time.
function Stories(){
const files = [ 'image1.jpg', 'image2.jpg' , 'video3.mp4' ]
const videoRef = useRef()
const imageRef = useRef()
const [ index , setIndex ] = useState(0); // the index starts at 0... first file...
const [ focus, setFocus ] = useState(null);
// Every time the index of the storie is changed. This effect happens.
useEffect(() => {
const video = videoRef.current;
const image = imageRef.current;
if( files[index].includes('mp4')){
video.style.display = "inline"
video.src = files[index];
// when i put it i put something in the "setFocus"
// triggers a useEffect for the "focus"
// that makes the setTimeOut which in the end runs
// this same script again after a certain time.
setFocus(files[index]);
// if there is any image in the DOM, hide it
if( image ) {
image.style.display = "none";
}
//In case the files [index] is an image.
} else {
image.style.display = "inline"
image.src = files[index];
setFocus(files[index])
// if there is any video in the DOM, hide it
if ( video ){
video.style.display = 'none';
video.muted = true
}
}
},[index])
function back(){
if( index <= 0 ) return
setIndex( index - 1);
}
function next(){
if ( index < files.length - 1) setIndex( index + 1 );
}
// This useeffect fires every time a file comes into focus.
useEffect(() => {
const timelapse = 5000;
// I wait for the video to finish to advance to the next index
if( files[index].includes('mp4')){
videoRef.current.addEventListener('ended',() => setIndex( index + 1 ));
}
// If it's an image, so..
else {
setTimeout(() => {
if( focus !== null && index < files.length - 1){
setIndex(index + 1);
}
}, timelapse )
}
},[focus])
// HTML
return <>
<video ref={videoRef}/>
<img ref={imageRef} />
<button onClick={back}> back </button>
<button onClick={next}> next </button>
</>
}
You can capture the ID of the timeout and cancel it like this:
const timeout = setTimeout(() => { console.log("woo") }, 5000)
window.clearTimeout(timeout)
So one approach would be to store that and cancel the timeout when your back or next functions are invoked.
Another approach would be to use state to record the remaining time and use a timer (setInterval) that counts down every second and advances the slide if it reaches zero. Then onClick you can just re-set the time remaining to 5 seconds.
const CarouselExample = () => {
const [time, setTime] = useState(5)
const [slide, setSlide] = setState(1)
useEffect(() => {
const countdown = window.setInterval(() => {
setTime(timeRemaining => {
if (timeRemaining - 1 < 0) {
setSlide(prevSlide => prevSlide + 1)
return 5
} else {
return timeRemaining - 1
}
})
}, 1000)
return () => {
window.clearInterval(countdown)
}
}, [])
return (
<div>
<div>Current Slide: {slide}</div>
<button
onClick={() => {
setTime(5)
setSlide(prev => prev + 1)
}}
>
Next
</button>
</div>
)
}