push() removing previous values - javascript

I'm trying to push new values to an array but all I'm getting is only the last value computed. I've looked at other answers but couldn't seem to figure it out. I appreciate the help thanks.
brief: upon clicking start I set a new date, then upon each time I click on the square div, the time is calculated between the previous date and the current date and the difference (diff) is oputput. I am attempting to save all the diff values into an array called diffArray using push() but only the last value is being saved/ output.
function App() {
const [startTime, setStartTime] = useState();
const [diff, setDiff] = useState();
const [gate, setGate] = useState(false);
const [left, setLeft] = useState(Math.floor(Math.random() * 1000));
const [top, setTop] = useState(Math.floor(Math.random() * 1000));
let diffArray = [];
const divStyle = {
height: 20,
width: 20,
top: top,
left: left,
position: "absolute",
backgroundColor: "brown"
};
const handleClick = () => {
setDiff((Date.now() - startTime) + '/ms');
if (diff !== undefined) {
diffArray.push(diff);
}
setStartTime(Date.now());
respawn();
ArrayMsOutput(diffArray);
}
const startClick = () => {
setGate(!gate);
setStartTime(Date.now());
}
const respawn = (e) => {
setLeft(Math.floor(Math.random() * 1000));
setTop(Math.floor(Math.random() * 900));
}
const ArrayMsOutput = (e) => {
return e;
}
return (
<div className="App">
<button onClick={startClick}>{gate ? 'Stop' : 'Start'}</button>
<div>{gate && diff}</div>
<div>{ArrayMsOutput()}</div>
{gate && <div onClick={handleClick} style={divStyle}>
</div>}
</div>
);
}
export default App;

const handleClick = () => {
setDiff((Date.now() - startTime) + '/ms');
if (diff !== undefined) {
diffArray.push(diff);
}
}
This won't work because your hook will have the value after the end of the function. You need to do:
const handleClick = () => {
const newDiff = (Date.now() - startTime) + '/ms';
setDiff(newDiff);
if (newDiff !== undefined) {
diffArray.push(newDiff);
}
}
Then, your array has only the latest value because you need to convert it to a useState hook: const [diffArray, setDiffArray] = useState([]) .
When you've done it, refacto your function to:
const handleClick = () => {
const newDiff = (Date.now() - startTime) + '/ms';
setDiff(newDiff);
if (newDiff !== undefined) {
setDiffArray(oldArray => [...oldArray, newDiff])
}
}

Related

React useEffect empty dependency

The problem: when I remove the dependency array in useEffect the timer never stops. But when I add a dependency array in useEffect the timer gets stuck on 5.
How can I solve this?
const App = () => {
const [inputValue, setInputValue] = useState("");
const [target, setTarget] = useState([]);
const [score, setScore] = useState(0);
const [timer, setTimer] = useState(5);
const newQuestion = () => {
const minimum = 1;
const maximum = 10;
const int1 = Math.floor(Math.random() * (maximum - minimum + 1)) + minimum;
const int2 = Math.floor(Math.random() * (maximum - minimum + 1)) + minimum;
setTarget([int1, int2]);
};
const handleReset = () => {
setScore(0);
setTimer(5);
};
useEffect(() => {
newQuestion();
}, [score]);
useEffect(() => {
let interval = setInterval(() => {
setTimer((prev) => {
if (prev === 1) clearInterval(interval);
return prev - 1;
});
}, 1000);
return () => clearInterval(interval);
});
const handleAnsewer = () => {
const total = target[0] + target[1];
if (total === Number(inputValue)) {
setScore(score + 1);
} else {
if (score > 0) {
setScore(score - 1);
}
}
setInputValue("");
newQuestion();
};
return (
<>
<h1>Random Math Quiz</h1>
<h1> {target.join(" + ")} </h1>
<h1> Timer: {timer} </h1>
<input placeholder="Answer" value={inputValue} onChange={(e) => setInputValue(e.target.value)} />
<div>
<button disabled={timer === 0} onClick={handleAnsewer}>
{" "}
Submit{" "}
</button>
</div>
<div>
<button onClick={handleReset}> play again </button>
</div>
{score === 10 && <h1> (Score: 10 / {score}) Congrate you are master in Math!</h1>}
{score <= 9 && timer < 1 && <h1> ( Score: 10 / {score}) Oh boy this is the math class!</h1>}
</>
);
};
export default App;
Change your useEffect containing the setInterval method with this one:
useEffect(() => {
if (timer > 0){
setTimeout(() => {setTimer(timer - 1)}, 1000)
}
}, [timer])
I think the approach with setTimeout() is better because you don't need to clear intervals or any of that nonsense.
How it works
Is the condition met?
If yes, update setTimer to new time timer - 1 after 1000ms
timer changes and so it will trigger useEffect and the component will re-render
This will go on until the timer doesn't change. When it hits 0

Unable to update useRef value in React hooks

What I am currently developing
I am using React hook to develop the function to reposition the DOM by drag and drop.
The code up to the halfway point can be found at this link.
https://codesandbox.io/s/exciting-williams-z4ngdz?file=/src/App.js
The code in question
I want to update the value of useRef() in React onMouseDown event, but I cannot update it and it remains at the initial value.
import axios from 'axios';
import React, { useEffect, useState, useRef } from 'react';
import './App.css';
const baseURL = new URL('https://api.ks-portfolio.site/skills');
/**
* #return {Promise<{Object}>}
*/
const apiGet = async () => await axios.get(baseURL);
/**
* #param {MouseEvent} event
* #param {HTMLElement} element
* #return {boolean}
*/
const isHover = (event, element) => {
const clientX = event.clientX;
const clientY = event.clientY;
const rect = element.getBoundingClientRect();
return clientY < rect.bottom && clientY > rect.top && clientX < rect.right && clientX > rect.left;
};
function App() {
const [blocks, setBlocks] = useState([]);
// Manage status with ref.
const state = useRef({
dndItems: [],
keys: new Map(),
dragElement: null,
canCheckHovered: true,
pointerPosition: { x: 0, y: 0 },
sortIds: [],
}).current;
const sortBlocks = (sortItems) => {
const canCheckTime = 300;
/**
* #param {MouseEvent} event
* #returns
*/
const onMouseMove = (event) => {
const { clientX, clientY } = event;
const { dndItems, dragElement, pointerPosition } = state;
if (!dragElement) return;
const x = clientX - pointerPosition.x;
const y = clientY - pointerPosition.y;
const dragStyle = dragElement.element.style;
dragStyle.zIndex = '100';
dragStyle.cursor = 'grabbing';
dragStyle.transform = `translate(${x}px,${y}px)`;
if (!state.canCheckHovered) return;
state.canCheckHovered = false;
setTimeout(() => state.canCheckHovered = true, canCheckTime);
const dragIndex = dndItems.findIndex(({ key }) => key === dragElement.key);
const hoveredIndex = dndItems.findIndex(({ element }, index) => index !== dragIndex && isHover(event, element));
if (hoveredIndex !== -1) {
state.pointerPosition.x = clientX;
state.pointerPosition.y = clientY;
dndItems.splice(dragIndex, 1);
dndItems.splice(hoveredIndex, 0, dragElement);
const { left: x, top: y } = dragElement.element.getBoundingClientRect();
dragElement.position = { x, y };
setBlocks(dndItems.map((item) => item.value));
}
};
/**
* #return {void}
*/
const onMouseUp = () => {
const { dragElement } = state;
if (!dragElement) return;
const dragStyle = dragElement.element.style;
dragStyle.zIndex = '';
dragStyle.cursor = '';
dragStyle.transform = '';
state.dragElement = null;
window.removeEventListener('mouseup', onMouseUp);
window.removeEventListener('mousemove', onMouseMove);
};
return sortItems.map((value) => {
const key = state.keys.get(`item_${value.language_id}`) || Math.random().toString(16);
state.keys.set(`item_${value.language_id}`, key);
if (!state.sortIds.includes(value.language_id)) {
state.sortIds.push(value.language_id);
}
return {
value,
key,
events: {
ref: (element) => {
const { dndItems, dragElement, pointerPosition, sortIds } = state;
if (!element || !dragElement) return;
const block = element.closest('.block');
block.style.transform = '';
const { left: x, top: y } = block.getBoundingClientRect();
const position = { x, y };
const itemIndex = dndItems.findIndex((item) => item.key === key);
if (itemIndex === -1 && sortIds.length > dndItems.length) {
return dndItems.push({ key, value, element: block, position });
}
if (dragElement.key === key) {
const dragX = dragElement.position.x - position.x;
const dragY = dragElement.position.y - position.y;
block.style.transform = `translate(${dragX}px,${dragY}px)`;
pointerPosition.x -= dragX;
pointerPosition.y -= dragY;
}
if (dragElement.key !== key) {
const item = dndItems[itemIndex];
const x = item.position.x - position.x;
const y = item.position.y - position.y;
block.style.transition = '';
block.style.transform = `translate(${x}px,${y}px)`;
requestAnimationFrame(() => {
block.style.transform = '';
block.style.transition = `all ${canCheckTime}ms`;
});
}
state.dndItems[itemIndex] = { key, value, element: block, position };
},
/**
* #param {React.MouseEvent<HTMLElement>} event
* #return {void}
*/
onMouseDown: (event) => {
const element = event.currentTarget.closest('.block');
state.pointerPosition.x = event.clientX;
state.pointerPosition.y = event.clientY;
element.style.transition = '';
element.style.cursor = 'grabbing';
const { left: x, top: y } = element.getBoundingClientRect();
const position = { x, y };
state.dragElement = { key, value, element, position };
console.log('No value is stored in dragElement.')
console.log('onMouseDown', {key, value, element, position});
console.log('state', state);
window.addEventListener('mouseup', onMouseUp);
window.addEventListener('mousemove', onMouseMove);
},
},
};
});
};
useEffect(() => {
async function fetchData() {
const response = await apiGet();
setBlocks(response.data[0].programming);
}
fetchData();
}, []);
return (
<ul className="App">
{sortBlocks(blocks).map((block,i) => {
const { language } = block.value;
return (
<li key={i} className="block">
<div className="panel-body" {...block.events}>
{language}
</div>
</li>
)
})}
</ul>
);
}
export default App;
Corresponding code
The dragElement on line 158 is null.
return sortItems.map((value) => {
events: {
ref: (element) => {
const { dndItems, dragElement, pointerPosition, sortIds } = state;
console.log("events", dragElement); // dragElement = null
}
}
Please let me know what the problem is and how to deal with it.

React timer does not update when there are other re-renders going on

I have a typing test web app built with a timer and text for the user to type. The user input is recorded via event listeners in a useEffect hook. The timer also uses an useEffect hook. Everything works just fine when they are separate, but when they are together and the user types the text, the timer is paused and does not rerender. It's not a delay. Not sure why this is happening, below is the code for the timer.
const Timer = ({initMin, initSec, setFin}) => {
const [ minutes, setMinutes ] = useState(parseInt(initMin));
const [ seconds, setSeconds ] = useState(parseInt(initSec));
// const [ timeEnd, setTimeEnd ] = useState(false);
useEffect(() => {
const myInterval = setInterval(() => {
if (seconds > 0) {
setSeconds( old => old - 1 );
}
if (seconds === 0) {
if (minutes === 0) {
setFin(true); // Ends the game
clearInterval(myInterval)
} else {
setMinutes( old => old - 1 );
setSeconds(59);
}
}
}, 1000)
return () => { clearInterval(myInterval); };
});
return (
<h1> {minutes}:{seconds < 10 ? `0${seconds}` : seconds} </h1>
);
}
And for the typing component.
export default function TypeTest({setStlLst, word}) {
const [test1Arr, setTest] = useState(word.split(' '));
const tempStyles = [];
for (var i = 0; i < test1Arr.length; ++i){
var letterStyles = [];
for (var j = 0; j < test1Arr[i].length; ++j){
if (!i && !j) letterStyles.push('current');
letterStyles.push('');
}
tempStyles.push(letterStyles);
}
const [styles, setStyles] = useState(tempStyles);
const [currWord, setWord] = useState(0);
const [currChar, setChar] = useState(0);
const [prevPos, setPrev] = useState(0);
useEffect(() => {
setStlLst(styles);
const alterStyle = (e) => {
var temp = styles.slice();
test1Arr[0][0] === e.key ? temp[0][0] = 'correct' : temp[0][0] = 'incorrect';
setStyles(temp);
}
window.addEventListener("keydown", alterStyle);
return () => window.removeEventListener("keydown", alterStyle);
}, [currChar, currWord, styles, prevPos, test1Arr, setStlLst]);
return (
<div className='outer-word-container'>
<div className="word-container">
{test1Arr.map((monoWord, idx) => <Word key={idx} inputWord = {monoWord} letterStyles={styles[idx]}/>)}
</div>
</div>
)
}
Any guidance would be appreciated!

using setInterval to increment variable every second - not updating

Inside my functional component I have defined two hooks started:false and sec:0
let interval = null
const Home = () => {
const [sec, setSec] = useState(0)
const [started, setStarted] = useState(false)
So as the name suggests, every second I want to increment this counter.
I have method called setTimer which should increment my sec every second.
function setTimer() {
console.log(started)
if (!started) {
console.log(started)
setStarted(true);
interval = setInterval(() => {
setSec(sec+1)
console.log("ADDED",sec)
}, 1000)
}
}
But it seems that the sec counter never goes above 1. Is there a reason for this?
You should uses a functional state update, so instead of setSec(sec+1) write setSec(prevSec => prevSec + 1)
See the React Hooks API for reference: https://reactjs.org/docs/hooks-reference.html#usestate
let started = false;
let sec = 0;
let setSec = function(seconds) { sec = seconds; }
let setStarted = function() { started = true; }
function setTimer() {
console.log(started)
if (!started) {
setStarted(true);
console.log(started)
interval = setInterval(() => {
setSec(sec+1)
console.log("ADDED",sec)
}, 1000)
}
}
setTimer();
var setStarted = false;
setSec = 0;
function setTimer() {
console.log(setStarted)
if (!setStarted) {
console.log(setStarted)
setStarted = true;
setInterval(() => {
setSec +=1;
console.log("ADDED",setSec);
}, 1000)
}
}
setTimer();
setInterval has closed over the initial value of your state(sec). Every time you are modifiying it, you are doing setSec(0+1), essentially making it 1. This is the problem of stale state.
You can use useRef to have access to the current value always.
import "./styles.css";
import { useState, useRef } from "react";
export default function App() {
const [sec, setSec] = useState(0);
const [started, setStarted] = useState(false);
let interval = null;
let realSec = useRef(0);
function setTimer() {
console.log(started);
if (!started) {
console.log(started);
setStarted(true);
interval = setInterval(() => {
setSec(realSec.current + 1);
realSec.current++;
console.log("ADDED", sec);
}, 1000);
}
}
return (
<>
<p>{sec}</p>
<button onClick={setTimer}>X</button>
</>
);
}

Fully functioning countdown timer using React hooks only

Here's my code. You can also check it out on Stackblitz:
import React, { useState } from 'react';
const Timer = ({
initialHours = 10,
initialMinutes = 0,
initialSeconds = 0,
}) => {
const [hours, setHours] = useState(initialHours);
const [minutes, setMinutes] = useState(initialMinutes);
const [seconds, setSeconds] = useState(initialSeconds);
let myInterval;
const startTimer = () => {
myInterval = setInterval(() => {
if (seconds > 0) {
setSeconds(seconds - 1);
}
if (seconds === 0) {
if (hours === 0 && minutes === 0) {
clearInterval(myInterval);
} else if (minutes > 0) {
setMinutes(minutes - 1);
setSeconds(59);
} else if (hours > 0) {
setHours(hours - 1);
setMinutes(59);
setSeconds(59);
}
}
}, 1000);
cancelTimer();
};
const cancelTimer = () => {
return () => {
clearInterval(myInterval);
};
};
return (
<div>
<h1 className='timer'>
{hours < 10 && hours !== 0 ? `0${hours}:` : hours >= 10 && `${hours}:`}
{minutes < 10 ? `0${minutes}` : minutes}:
{seconds < 10 ? `0${seconds}` : seconds}
</h1>
<button onClick={startTimer}>START</button>
<button>PAUSE</button>
<button>RESUME</button>
<button onClick={cancelTimer}>CANCEL</button>
</div>
);
};
export default Timer;
I'm having trouble with the START button and this is what it looks like when I click on the START button multiple times:
You'll notice that on the first click the number never continues to go down unless I click on the START button again and again but it would look like a broken slot machine. And if I hit on the CANCEL button, it should stop the timer and reset back to the set time, but it doesn't. I don't know how to solve the problem for this 2 buttons, and much more for the PAUSE and RESUME. I don't know how to make them work, too. Please help.
As suggested by #Felix Kling you can try a different approach, to check why your code is not working check the below code, I've made some changes in your Timer component :
import React, { useState } from 'react';
const Timer = ({
initialHours = 10,
initialMinutes = 0,
initialSeconds = 0,
}) => {
const [time, setTime] = useState({
h: initialHours,
m: initialMinutes,
s: initialSeconds,
});
const [timer, setTimer] = useState(null);
const startTimer = () => {
let myInterval = setInterval(() => {
setTime((time) => {
const updatedTime = { ...time };
if (time.s > 0) {
updatedTime.s--;
}
if (time.s === 0) {
if (time.h === 0 && time.m === 0) {
clearInterval(myInterval);
} else if (time.m > 0) {
updatedTime.m--;
updatedTime.s = 59;
} else if (updatedTime.h > 0) {
updatedTime.h--;
updatedTime.m = 59;
updatedTime.s = 59;
}
}
return updatedTime;
});
}, 1000);
setTimer(myInterval);
};
const pauseTimer = () => {
clearInterval(timer);
};
const cancelTimer = () => {
clearInterval(timer);
setTime({
h: initialHours,
m: initialMinutes,
s: initialSeconds,
});
};
return (
<div>
<h1 className='timer'>
{time.h < 10 && time.h !== 0
? `0${time.h}:`
: time.h >= 10 && `${time.h}:`}
{time.m < 10 ? `0${time.m}` : time.m}:
{time.s < 10 ? `0${time.s}` : time.s}
</h1>
<button onClick={startTimer}>START</button>
<button onClick={pauseTimer}>PAUSE</button>
<button onClick={cancelTimer}>CANCEL</button>
</div>
);
};
export default Timer;
Explanation:
in your startTimer function in the last line you're calling cancelTimer
When you're working with hooks then keep in mind you won't get updated value of state variable until you use function inside a set function like I'm doing in setTime and in that callback, you'll get an updated value as a first parameter
In cancelTimer method you're returning a function you've to call clearInterval also myInterval is undefined in cancelTimer so I've set it's value in state
For more information and other ways check this question

Categories

Resources