I have an image slider now, but the images change only at the click of a button, and I want to add this function so that it works automatically:
var timer;
timer = setInterval(function() {
plusSlides(1);
}, 5000);
This is the React image slider code (full demo at https://codepen.io/anon/pen/dBmvje):
function Slider({ items }) {
const [ active, setActive ] = React.useState(0);
const { length, [active]: slide } = items;
const next = e => setActive((active + +e.target.dataset.step + length) % length);
const goTo = e => setActive(+e.target.dataset.index);
return (
<div>
<div className="slideshow-container">
<div className="mySlides fade">
<div className="numbertext">{active + 1} / {length}</div>
<img src={slide.img} />
<div className="text">{slide.title}</div>
</div>
<a className="prev" onClick={next} data-step={-1}>❮</a>
<a className="next" onClick={next} data-step={+1}>❯</a>
</div>
<div className="dots">
{items.map((n, i) => (
<span
key={n.id}
className={`dot ${i === active ? 'active' : ''}`}
onClick={goTo}
data-index={i}
></span>
))}
</div>
</div>
);
}
And here is the whole Javascript code from where I "borrowed" this function from, so that it is clear where the "plusSlides (1);" function came from :
var timer;
var slideIndex = 1;
showSlides(slideIndex);
function plusSlides(n) {
showSlides((slideIndex += n));
}
function showSlides(n) {
var i;
var slides = document.getElementsByClassName("mySlides");
if (n > slides.length) {
slideIndex = 1;
}
if (n < 1) {
slideIndex = slides.length;
}
for (i = 0; i < slides.length; i++) {
slides[i].style.display = "none";
}
slides[slideIndex - 1].style.display = "block";
}
timer = setInterval(function() {
plusSlides(1);
}, 5000);
In functional React components, you can set up side effects with the effect hook (React.useEffect). Add this code before the return statement:
React.useEffect(() => {
const timeout = setTimeout(() => setActive((active + 1 + length) % length), 5000);
return () => clearTimeout(timeout);
}, [active, length]);
The first argument to useEffect is the function to run, and the second is the list of dependencies (the function will re-run if a dependency changes). The return value from the function tells how to clean up the effect when the component is unmounted or one of the dependencies changes.
Due to the requirement of listing the effect's dependencies, it seemed better to use setTimeout rather than setInterval in this case. When the timeout runs, the value of active will change, causing the effect function to re-run and start another timeout. A side benefit of this approach is it works better with user interaction: if the user manually advances the slide, the timeout will be reset and the slide will wait 5s to change again (whereas with the interval, the slide might change again right away if the user happened to click right before the interval's next scheduled run).
Related
Being new to react , this all is really confusing and new to me , so I apologise if I'm making some obvious oversight.
Im making a stopwatch and implementing the seconds for starters. However; Im confused as to how i'll implement the on display seconds number to update when each second passes.
This is what I'm doing right now
function App() {
const [time , updateTime] = React.useState(0);
var startsec = 0;
//UpdateTime should get triggered when next second passes
const UpdateTime = () => {
//Update time variable with the new seconds elapsed
}
//Should run every second or something
const CheckTimeUpdation = () => {
currentsec = Math.floor(Date.now() / 1000.0);
console.log(currentsec);
if(currentsec > startsec){
UpdateTime(currentsec-startsec);
}
}
const GetStartTime = () => {
startsec = Math.floor(Date.now() / 1000.0);
}
//Clock component just gets a number and displays it on the screen
return (<div className = "App">
<Clock timerSeconds= {time}/>
<div>
<button onClick={GetStartTime}></button>
</div>
</div>);
}
export default App;
Date.now() function gets the miliseconds passed since 1970 (hence the division by 1000 to make them into seconds) and I find the difference between when the button was clicked and current one and passs that to the time component to display.
How do I make the CheckTimeUpdation function run every second or so?
What you want is the setInterval() method (see: https://developer.mozilla.org/en-US/docs/Web/API/setInterval)
However your code so far has some issues:
On the button click, getStartTime runs and it updates the value of startsec. Firstly, this does not cause the component to re-render, and so the component will not update and you will see nothing changing on your screen. Also, if you did get your component to re-render, you will notice that startsec will be 0 again on the next re-render, so re-assigning startsec like how you did likely doesn't do what you want it to. If you want to persist values between rerenders, you can use useState (https://reactjs.org/docs/hooks-reference.html#usestate) or useRef (https://reactjs.org/docs/hooks-reference.html#useref).
Now i'm assuming you want to start the timer when the button is clicked. What you need is to start the interval (via setInterval) on the button click, and update time every 1000ms.
You'd also want to clear the interval once you don't need it anymore, using the clearInterval() method. You'll need to save the id returned from setInterval() initially in order to clear it later.
Below I have written a short example using this idea with setInterval on button click to help you:
import { useState, useRef } from "react";
export default function App() {
const [timerState, setTimerState] = useState("paused");
const [timeElapsed, setTimeElapsed] = useState(0);
const intervalId = useRef(0);
const isRunning = timerState === "running";
const isPaused = timerState === "paused";
const onStartTimer = () => {
intervalId.current = setInterval(() => {
setTimeElapsed((time) => time + 1);
}, 1000);
setTimerState("running");
};
const onStopTimer = () => {
clearInterval(intervalId.current);
intervalId.current = 0;
setTimerState("paused");
};
const onClearTimer = () => {
setTimeElapsed(0);
};
return (
<div className="App">
<h1>{timeElapsed}</h1>
{isPaused && <button onClick={onStartTimer}>Start timer</button>}
{isRunning && <button onClick={onStopTimer}>Stop timer</button>}
{!!timeElapsed && <button onClick={onClearTimer}>Clear timer</button>}
</div>
);
}
You can use setInterval in the GetStartTime Function.
const GetStartTime = () => {
startsec = Math.floor(Date.now() / 1000.0);
setInterval(() => CheckTimeUpdation(), 1000);
}
I'm quite new to ReactJS and have been working on a Pomodoro Timer. How it works is that whenever the "Work Timer" reaches zero, it switches to the "Break Timer".
const [secondsLeft, setSecondsLeft] = useState(newTimer.work);
However,
this line: setSecondsLeft(nextSeconds); // This does not update secondsLeft
under useEffect, does not update the secondsLeft which results in the timer for the break to display wrongly.
Could use some advice to make this work.
Thanks!
import {
Button,
Heading,
VStack,
Stack,
HStack,
Box,
CircularProgress,
Text,
CircularProgressLabel,
} from '#chakra-ui/react';
import { useState, useRef, useContext, useEffect } from 'react';
import { SettingsContext } from '../helpers/SettingsContext';
import PomodoroSettings from './PomodoroSettings';
// Pomodoro
const Pomodoro = () => {
//Timer
const { newTimer, setNewTimer } = useContext(SettingsContext);
const [isPaused, setIsPaused] = useState(true);
const [secondsLeft, setSecondsLeft] = useState(newTimer.work);
const secondsLeftRef = useRef(secondsLeft);
const isPausedRef = useRef(isPaused);
const modeRef = useRef(newTimer.active);
function tick() {
secondsLeftRef.current--;
setSecondsLeft(secondsLeftRef.current);
}
useEffect(() => {
function switchMode() {
const nextMode = modeRef.current === 'work' ? 'break' : 'work';
const nextSeconds =
(nextMode === 'work' ? newTimer.work : newTimer.short) * 60;
setNewTimer({
work: newTimer.work,
short: newTimer.short,
long: newTimer.long,
active: nextMode,
});
modeRef.current = nextMode;
console.log(nextMode);
console.log('Next: ' + nextSeconds);
setSecondsLeft(nextSeconds); // This does not update secondsLeft
console.log('SecondsLeft: ' + secondsLeft);
secondsLeftRef.current = nextSeconds;
}
secondsLeftRef.current = newTimer.work * 60;
setSecondsLeft(secondsLeftRef.current);
const interval = setInterval(() => {
if (isPausedRef.current) {
return;
}
if (secondsLeftRef.current === 0) {
return switchMode();
}
tick();
}, 1000);
return () => clearInterval(interval);
}, [newTimer, setNewTimer]);
const totalSeconds =
newTimer.active === 'work' ? newTimer.work * 60 : newTimer.short * 60;
const percentage = Math.round((secondsLeft / totalSeconds) * 100);
const minutes = Math.floor(secondsLeft / 60);
let seconds = secondsLeft % 60;
if (seconds < 10) seconds = '0' + seconds;
// Start / Pause
const handleButton = (e) => {
e.preventDefault();
if (isPaused) {
setIsPaused(!isPaused);
isPausedRef.current = false;
} else {
setIsPaused(!isPaused);
isPausedRef.current = true;
}
};
// console.log(percentage)
// console.log(newTimer.active)
// console.log(secondsLeft)
// console.log(totalSeconds)
return (
<div>
<Heading as='h4' size='md'>
{' '}
Pomodoro{' '}
</Heading>
<VStack>
<HStack>
<Button variant='ghost'>Pomodoro</Button>
<Button variant='ghost'>Short Break</Button>
<Button variant='ghost'>Long Break</Button>
</HStack>
<CircularProgress
value={percentage}
color={newTimer.active === 'work' ? 'red.400' : 'green.400'}
size='200px'
thickness='10px'
>
<CircularProgressLabel>
<Text fontSize='3xl'>{minutes + ':' + seconds}</Text>
</CircularProgressLabel>
</CircularProgress>
<HStack>
<Button variant='outline' onClick={handleButton}>
{isPaused ? <div> START </div> : <div> STOP </div>}
</Button>
<PomodoroSettings />
</HStack>
</VStack>
</div>
);
};
export default Pomodoro;
useState uses array const [value, setValue] like this. It might be just an error in this declaration of newTimer.
There is too much going on in one eseEffect(). The initial value of newTimer.work is not modified so each re-render will set secondsLeftRef to the same initial value.
Since useEffect()updates a context object, the component will re-render on every iteration, setting secondsLeftRef to its initial value.
Not sure where the problem is but few things I noticed that you can try to fix or improve in your code -
Do not use or assign context or props value in useState as you are doing it for secondsLeft, reason is that useState is supposed to run once when component gets rendered first time and you might not get value for newTimer.work on very first render.
For this you can use useEffect and newTimer in dependency array.
const [secondsLeft, setSecondsLeft] = useState(newTimer.work);
You are trying to log secondsLeft immediatly after calling setSecondsLeft this will not give you correct value because setting state is asynchronous in react so it will not be available on next line after calling set state.
setSecondsLeft(nextSeconds);
console.log('SecondsLeft: ' + secondsLeft);
One last thing I noticed looking at your code is that it's possible that you might be registering multiple setInterval because below line is responsible for clearing interval only when component gets unmount but your useEffect will get called multiple times based on its dependencies.
return () => clearInterval(interval);
Try to debug your code for above 3rd point and if that's the problem you
can try clearing your interval in start of useEffect or add
some conditions to make sure it will get register once to get
expected results.
Note - I have not tried executing your code, but let me know if this helps or if I can improve my answer in any way.
I am trying to set up an image carousel that loops through 3 images when you mouseover a div. I'm having trouble trying to figure out how to reset the loop after it reaches the third image. I need to reset the setInterval so it starts again and continuously loops through the images when you are hovering over the div. Then when you mouseout of the div, the loop needs to stop and reset to the initial state of 0. Here is the Code Sandbox:
https://codesandbox.io/s/pedantic-lake-wn3s7
import React, { useState, useEffect } from "react";
import { images } from "./Data";
import "./styles.css";
export default function App() {
let timer;
const [count, setCount] = useState(0);
const updateCount = () => {
timer = setInterval(() => {
setCount((prevCount) => prevCount + 1);
}, 1000);
if (count === 3) clearInterval(timer);
};
const origCount = () => {
clearInterval(timer);
setCount((count) => 0);
};
return (
<div className="App">
<div className="title">Image Rotate</div>
<div onMouseOver={updateCount} onMouseOut={origCount}>
<img src={images[count].source} alt={images.name} />
<p>count is: {count}</p>
</div>
</div>
);
}
Anything involving timers/intervals is an excellent candidate for useEffect, because we can easily register a clear action in the same place that we set the timer using effects with cleanup. This avoids the common pitfalls of forgetting to clear an interval, e.g. when the component unmounts, or losing track of interval handles. Try something like the following:
import React, { useState, useEffect } from "react";
import { images } from "./Data";
import "./styles.css";
export default function App() {
const [count, setCount] = useState(0);
const [mousedOver, setMousedOver] = useState(false);
useEffect(() => {
// set an interval timer if we are currently moused over
if (mousedOver) {
const timer = setInterval(() => {
// cycle prevCount using mod instead of checking for hard-coded length
setCount((prevCount) => (prevCount + 1) % images.length);
}, 1000);
// automatically clear timer the next time this effect is fired or
// the component is unmounted
return () => clearInterval(timer);
} else {
// otherwise (not moused over), reset the counter
setCount(0);
}
// the dependency on mousedOver means that this effect is fired
// every time mousedOver changes
}, [mousedOver]);
return (
<div className="App">
<div className="title">Image Rotate</div>
<div
// just set mousedOver here instead of calling update/origCount
onMouseOver={() => setMousedOver(true)}
onMouseOut={() => setMousedOver(false)}
>
<img src={images[count].source} alt={images.name} />
<p>count is: {count}</p>
</div>
</div>
);
}
As to why your code didn't work, a few things:
You meant to say if (count === 2) ..., not count === 3. Even better would be to use the length of the images array instead of hardcoding it
Moreover, the value of count was stale inside of the closure, i.e. after you updated it using setCount, the old value of count was still captured inside of updateCount. This is actually the reason to use functional state updates, which you did when you said e.g. setCount((prevCount) => prevCount + 1)
You would have needed to loop the count inside the interval, not clear the interval on mouse over. If you think through the logic of it carefully, this should hopefully be obvious
In general in react, using a function local variable like timer is not going to do what you expect. Always use state and effects, and in rarer cases (not this one), some of the other hooks like refs
I believe that setInterval does not work well with function components. Since callback accesses variables through closure, it's really easy to shoot own foot and either get timer callback referring to stale values or even have multiple intervals running concurrently. Not telling you cannot overcome that, but using setTimeout is much much much easier to use
useEffect(() => {
if(state === 3) return;
const timerId = setTimeout(() => setState(old => old + 1), 5000);
return () => clearTimeout(timerId);
}, [state]);
Maybe in this particular case cleanup(clearTimeout) is not required, but for example if user is able to switch images manually, we'd like to delay next auto-change.
The timer reference is reset each render cycle, store it in a React ref so it persists.
The initial count state is closed over in interval callback scope.
There are only 3 images so the last slide will be index 2, not 3. You should compare against the length of the array instead of hard coding it.
You can just compute the image index by taking the modulus of count state by the array length.
Code:
export default function App() {
const timerRef = useRef();
const [count, setCount] = useState(0);
// clear any running intervals when unmounting
useEffect(() => () => clearInterval(timerRef.current), []);
const updateCount = () => {
timerRef.current = setInterval(() => {
setCount((count) => count + 1);
}, 1000);
};
const origCount = () => {
clearInterval(timerRef.current);
setCount(0);
};
return (
<div className="App">
<div className="title">Image Rotate</div>
<div onMouseOver={updateCount} onMouseOut={origCount}>
<img
src={images[count % images.length].source} // <-- computed index to cycle
alt={images.name}
/>
<p>count is: {count}</p>
</div>
</div>
);
}
Your setCount should use a condition to check to see if it should go back to the start:
setCount((prevCount) => prevCount === images.length - 1 ? 0 : prevCount + 1);
This will do setCount(0) if we're on the last image—otherwise, it will do setCount(prevCount + 1).
A faster (and potentially more readable) way of doing this would be:
setCount((prevCount) => (prevCount + 1) % images.length);
I have a hard time trying to understand "useState" and when a render is triggered. I want to make a drag and drop system where the user can drag elements from one box to another.
I have successfully done this by using pure javascript, but as usual, it gets messy fast and I want to keep it clean. I thought since I'm using react I should do it using UseState, and I've got the array to update the way I want it to but the changes don't render.
Shouldn't I use useState in this way? What should I use instead? I don't want to solve it the "hacky" way, I want it to be proper.
const [allComponents, updateArray] = useState([])
function arrayMove(array, closest) {
let moveChild = array[parentIndex].children[childIndex]
array[parentIndex].children = array[parentIndex].children.filter(function(value, index, arr){ return index != childIndex;});
array[closest].children = [...array[closest].children, moveChild];
return array;
}
var lastClosest = {"parent": null, "child": null};
var parentIndex = null;
var childIndex = null;
function allowDrop(ev, index) {
ev.preventDefault();
if(allComponents.length > 0) {
let closest = index;
if((parentIndex == lastClosest.parent && childIndex == lastClosest.child) || (closest == parentIndex)) {
return;
}
lastClosest.parent = parentIndex;
lastClosest.child = childIndex;
updateArray(prevItems => (arrayMove(prevItems, closest)));
}
}
function dragStart(pI, cI, ev) {
parentIndex = pI;
childIndex = cI;
}
function drop(ev) {
ev.preventDefault();
}
function addNewSpace() {
updateArray(prevItems => [...prevItems, {"name": "blaab", "children": [{"child": "sdaasd", "type": "text"}]}]);
}
return (
<div className={styles.container}>
<button onClick={addNewSpace}>Add</button>
<div className={styles.spacesWrapper}>
{allComponents.map(({name, children}, index) => (
<div key={"spacer_" + index} onDragOver={(event) => allowDrop(event, index)} onDrop={(event) => drop(event)} className={styles.space}>
{children.map(({type, child}, index2) => (
<div id ={"movable_" + index + ":" + index2} key={"movable_" + index2} className={styles.moveableOne}>
<div key={"draggable_" + index2} draggable="true" onDrag={(event) => onDrag(event)} onDragStart={(event) => dragStart(index, index2, event)}>
</div>
</div>
))}
</div>
))}
</div>
</div>
)
}
Here is the problem I'm experiencing with the nested array not updating properly:
A react page re-renders every time the state changes. So every time a setState is called, the react component re-renders itself and updates the page to reflect the changes.
You can also see how the state is updating if you install the react dev tools chrome extension.
Try to modify your code with the below code and see if it solves your issue,
import React, { useState, useEffect } from "react";
let newAllComponents = [...allComponents];
useEffect(() =>
{
newAllComponents = [...allComponents]
}, [allComponents]);
/* div inside return*/
{newAllComponents.map(({ name, children }, index) => (
<div
key={"spacer_" + index}
onDragOver={(event) => allowDrop(event, index)}
onDrop={(event) => drop(event)}
>
It keeps showing the error message that it is an infinite loop. I am only beggining to learn React, and this is a Clicker game. How do I change my code to make the setInterval work. Thank you.(BTW I do not want any other changes to the code that won't affect the setInterval function. And yes, I have used setInterval in many projects already and it worked out fine.)
import "./styles.css";
export default function App() {
let [num, setNum] = useState(0);
let [add, setAdd] = useState(1);
let [numC, setNumC] = useState(0);
let [numCP, setNumCP] = useState(10);
let [numW, setNumW] = useState(0);
let [numWP, setNumWP] = useState(20)
setInterval(setNum(num+=numW),3000);
const click = () => {
setNum((num += add));
};
const clicker = () => {
if (num >= numCP) {
setNumC((numC += 1));
setNum((num -= numCP));
setNumCP((numCP += 5));
setAdd((add += 1));
}
};
const worker = () => {
if (num >= numWP) {
setNumW((numW += 1));
setNum((num -= numWP));
setNumWP((numWP += 10));
}
};
return (
<div className="App">
<h1>Clicker Game</h1>
<div>
{num}
<button onClick={click}>Click</button>
</div>
<p />
<div>
{numC}
<button onClick={clicker}>Buy({numCP})</button>
</div>
<div>
{numW}
<button onClick={worker}>Buy({numWP})</button>
</div>
</div>
);
}```
There are a couple of issues.
First you are immediately calling the setNum when you should be passing a callback to be executed when the interval is passed.
So setInterval(() => setNum(num+=numW),3000);
But now you have the second issue, each time the component is re-rendered you will initiate an additional interval. (and it will be re-rendered a lot, at the minimum each time the interval callback is fired)
So you would likely need to use a useEffect, with 0 dependencies so it runs once, if you want to set it and let it run continuously.
useEffect(() => {
setInterval(() => setNum(num += numW), 3000);
}, []);
But now you will encounter yet another issue. The num and numW used in the interval will be locked to the values in the first render of the component.
For the num you can work around it, by using the callback syntax of the setNum
useEffect(() => {
setInterval(() => setNum(currentNum => currentNum += numW), 3000);
}, []);
but numW will never update.
A final tool, is to reset the interval each time the numW or num changes. To do that you will need to return a function from the useEffect that does the clearing.
useEffect(() => {
const interval = setInterval(() => setNum(currentNum => currentNum += numW), 3000);
return () => clearInterval(interval);
}, [numW]);
But this will have the minor issue that the interval is now not constant, since it resets.
Every time one of your state variables changes, the component is re-rendered, i.e. the function App is called again. This will call setInterval over and over again.
You want to look at useEffect to put your setIntervals in.