OnScroll Event is cycling when scrolled programmicaly inside - javascript

When I create a Scroll Listener which scrolls to specific position inside, it triggers itself again even when I catch the first recall at the beginning. Idk what could cause this.
const Example: React.FC<Props> = () => {
let _preventScroll = false;
let _currentParagraph = 0;
let _lastScrollTop = 0;
const _paragraphs = [
useRef<HTMLDivElement>(null),
useRef<HTMLDivElement>(null),
useRef<HTMLDivElement>(null),
useRef<HTMLDivElement>(null),
useRef<HTMLDivElement>(null),
useRef<HTMLDivElement>(null),
];
useEffect(() => {
const onScroll = (ev: Event) => {
if (_preventScroll) {
ev.preventDefault();
_preventScroll = false;
return;
}
var st = window.pageYOffset || document.documentElement.scrollTop;
if (st > _lastScrollTop && _currentParagraph < 5) {
_currentParagraph++;
} else if (_currentParagraph > 0) {
_currentParagraph--;
}
_preventScroll = true;
_paragraphs[_currentParagraph].current?.scrollIntoView({
behavior: "smooth",
block: "start",
});
_lastScrollTop = st <= 0 ? 0 : st;
};
window.addEventListener("scroll", onScroll);
return () => {
window.removeEventListener("scroll", onScroll);
};
}, []);
return (
<>
<div style={{height: "100vh"}} ref={paragraph[0]} />
<div style={{height: "100vh"}} ref={paragraph[1]} />
<div style={{height: "100vh"}} ref={paragraph[2]} />
<div style={{height: "100vh"}} ref={paragraph[3]} />
<div style={{height: "100vh"}} ref={paragraph[4]} />
</>
);
};
export default Example;
I tried to add an Catch of the first recall but after this another happen from nowhere.
May there is a Issue with Bubbling idk.

Related

React iterating over list and applying animation with delay between each iteration

I am having troubles fixing a bug basically I need to iterate over a list of buttons and apply an animation and on the next iteration I remove the animation from the previous element, however, when running the code the animation is started twice at the beginning and one element remains stuck with the animation applied.
The following is the code of the component:
import type { NextPage } from 'next'
import Head from 'next/head'
import { useRouter } from 'next/router'
import { useEffect, useRef, useState } from 'react'
import { IoCheckmark, IoClose, IoHome, IoRefresh } from 'react-icons/io5'
import Page from '../components/page/Page'
import styles from '../styles/Play.module.css'
import { distance } from '../utils/distance'
import { randomInt, randomFloat } from '../utils/random'
function ShowSequence(props: any) {
const [index, setIndex] = useState(0);
const [sequence, setSequence] = useState(props.sequence);
const [timer, setTimer] = useState<any>();
useEffect(() => {
console.log(index)
if (index > 0) document.getElementById(sequence[index - 1])?.classList.toggle(styles.animate);
if (index < sequence.length) document.getElementById(sequence[index])?.classList.toggle(styles.animate);
else return clearInterval(timer);
setTimer(setTimeout(() => setIndex(index + 1), 3000));
}, [index]);
return <div className={styles.button}>
{
props.map ? props.map.map((button: any) => {
return <button key={button.buttonId} className={styles.button} id={button.buttonId} style={{ top: button.y + "px", left: button.x + "px", backgroundColor: button.color }}></button>
}) : null
}
</div>;
}
function DoTask(props: any) {
return <div>
</div>;
}
function ChooseSequence(props: any) {
const [sequence, setSequence] = useState(props.sequence);
const [index, setIndex] = useState(0);
const [timer, setTimer] = useState<any>();
const [buttonMap, setButtonMap] = useState<any>({});
console.log(sequence);
return <div className={styles.button}>
{
props.map ? props.map.map((button: any) => {
return <button key={button.buttonId} className={styles.button} id={button.buttonId} style={{ top: button.y + "px", left: button.x + "px", backgroundColor: button.color }} onClick={(e) => {
let correctSequence = sequence[index] === button.buttonId;
e.currentTarget.classList.toggle(correctSequence ? styles.correctButton : styles.wrongButton);
buttonMap[button.buttonId] = correctSequence ? <IoCheckmark size={20} color={"white"}></IoCheckmark> : <IoClose size={20} color={"white"}></IoClose>;
setButtonMap(buttonMap);
setIndex(index + 1);
}}>
{ (buttonMap[button.buttonId]) ? buttonMap[button.buttonId] : button.buttonId }
</button>
}) : null
}
</div>;
}
function Error(props: any) {
return <div className={styles.errorMenu}>
<h1>You lost!</h1>
<p>You reached level: {props.level}</p>
<div className={styles.container}>
<div className={styles.item}></div>
<div className={styles.item}></div>
<div className={styles.item}></div>
<div className={styles.item}></div>
<div className={styles.item}></div>
<div className={styles.item}></div>
<div className={styles.item}></div>
<div className={styles.item}></div>
<div className={styles.item}></div>
<div className={styles.item}></div>
</div>
<div className={styles.row}>
<button className={styles.retryButton} onClick={() => window.location.href = "/play"}><IoRefresh></IoRefresh></button>
<button className={styles.closeButton} onClick={() => window.location.href = "/"}><IoHome></IoHome></button>
</div>
</div>;
}
enum State {
SHOWSEQUENCE,
DOTASK,
CHOOSESEQUENCE,
ERROR
}
const Play: NextPage = () => {
let [state, setState] = useState<State>(State.SHOWSEQUENCE);
let [sequence, setSequence] = useState<number[]>([randomInt(1, 20), randomInt(1, 20), randomInt(1, 20), randomInt(1, 20)]);
let [map, setMap] = useState<any[]>();
let [level, setLevel] = useState(1);
let component;
useEffect(() => {
if (state === State.SHOWSEQUENCE) {
let newSequenceId = randomInt(1, 20);
setSequence((prevSequence: number[]) => [...prevSequence, newSequenceId])
}
}, [state]);
useEffect(() => {
let buttonIds = Array.from({ length: 20 }, (v, k) => k + 1);
const { innerWidth, innerHeight } = window;
let colors: string[] = ["#c0392b", "#e67e22", "#27ae60", "#8e44ad", "#2c3e50"];
let buttonMap: any[] = [];
let rows = buttonIds.length / 10;
let columns = rows > 0 ? buttonIds.length / rows : buttonIds.length;
for (let row = 0; row < rows; row++) {
for (let col = 0; col < columns; col++) {
let color = colors[Math.floor(randomFloat() * colors.length)];
let x = innerWidth / columns * col + 100;
let y = innerHeight / rows * row + 100;
let offsetX = (randomFloat() < .5) ? -1 : 1 * randomFloat() * ((innerWidth / columns) - 100);
let offsetY = (randomFloat() < .5) ? -1 : 1 * randomFloat() * ((innerHeight / rows) - 100);
if (x + offsetX + 100 > innerWidth) offsetX -= ((x + offsetX) - innerWidth) + 100;
if (y + offsetY + 100 > innerHeight) offsetY -= ((y + offsetY) - innerHeight) + 100;
buttonMap.push({ buttonId: buttonIds[row * columns + col], x: x + offsetX, y: y + offsetY, color })
}
}
setMap(buttonMap);
}, [])
switch (state) {
case State.SHOWSEQUENCE:
component = <ShowSequence map={map} sequence={sequence} changeState={() => setState(State.DOTASK)}></ShowSequence>;
break;
case State.DOTASK:
component = <DoTask changeState={() => setState(State.CHOOSESEQUENCE)} onError={() => setState(State.ERROR)}></DoTask>
break;
case State.CHOOSESEQUENCE:
component = <ChooseSequence map={map} sequence={sequence} changeState={() => setState(State.SHOWSEQUENCE)} onError={() => setState(State.ERROR)}></ChooseSequence>
break;
}
return (
<Page color="blue">
{ state === State.ERROR ? <Error level={level}></Error> : null }
{component}
</Page>
)
}
export default Play
Here is a codesandbox.
Instead of querying the DOM, an anti-pattern in React, you should add the appropriate classname when mapping the data. This avoids the DOM mutations and handles the conditional logic for adding and removing the "animate" class.
Use a functional state update to increment the index.
I suggest using a React ref to hold a reference to the interval timer so it's not triggering additional rerenders
Return a cleanup function to clear any running timers when necessary, i.e. when the component unmounts.
Code:
function ShowSequence(props) {
const [index, setIndex] = useState(0);
const timerRef = useRef();
useEffect(() => {
console.log(index);
timerRef.current = setTimeout(() => setIndex((index) => index + 1), 3000);
return () => {
clearTimeout(timerRef.current);
};
}, [index]);
return (
<div className={styles.button}>
{props.map?.map((button, i) => {
return (
<button
key={button.buttonId}
className={[styles.button, i === index ? styles.animate : null]
.filter(Boolean)
.join(" ")}
id={button.buttonId}
style={{
top: button.y + "px",
left: button.x + "px",
backgroundColor: button.color
}}
></button>
);
})}
</div>
);
}
To cleanup the timer in the useEffect, you must return a function.
The previous element index is 1 less the current index for non-zero indexes or the last element in the array of buttons when the current index is the first item.
const prevElIndex = index == 0 ? props.map.length - 1 : index - 1;
Also, you need to check if the previous element has the animation class before toggling the class. This takes care of the first time the animation starts to run (the previous element would not have the animation class).
if (
document
.getElementById(sequence[prevElIndex])
?.classList.contains(styles.animate)
) {
document
.getElementById(sequence[prevElIndex])
?.classList.toggle(styles.animate);
}
Altogether, your effect would be along these lines:
const [index, setIndex] = useState(0);
const [sequence, setSequence] = useState(props.sequence);
const [timer, setTimer] = useState<number>();
useEffect(() => {
const prevElIndex = index == 0 ? props.map.length - 1 : index - 1;
if (
document
.getElementById(sequence[prevElIndex])
?.classList.contains(styles.animate)
) {
document
.getElementById(sequence[prevElIndex])
?.classList.toggle(styles.animate);
}
document.getElementById(sequence[index])?.classList.toggle(styles.animate);
setTimer(setTimeout(() => setIndex((index + 1) % sequence.length), 3000));
return () => clearTimeout(timer);
}, [index]);
A working Stackblitz showing this in action.

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?

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;
}

Cannot read property 'style' of undefined in useEffect

Currently I'm trying to change the layout of my webpage according to the width of the webpage. So if it is higher that a certain pixels, it should show a different view, but if its lower, then it should show a different view. To achieve this I have tried using useState and useEffect to get the window.innerWidth and then placing conditions on my return statement but this brings up a Cannot read property 'style' of undefined.
Code:
export default function App() {
const [screen, setScreen] = useState(false);
const [width, setWidth] = useState(0);
useEffect(() => {
setWidth(window.innerWidth);
});
const ref = useRef(null);
// Reduce value if want the image to be closer to the edges
// otherwise to the center
const setImageLimitMovement = 1;
const setTextLimitMovement = 4;
const opacityRange = 400;
// Speed text movement
const speed = 1; // .5
useEffect(() => {
window.addEventListener("resize", () => {
if (window.innerWidth !== 0) {
setScreen(window.innerWidth);
}
});
}, []);
useEffect(() => {
const app = [...ref.current.children];
const titles = app.filter((el) => el.matches(".titles") && el);
const blocks = app.filter((el) => el.matches(".blocks") && el);
const img = app.find((el) => el.matches("#passport") && el);
// Get the center point of blocks in an array
const centerPoints = blocks.map((blockEl, idx) => {
const blockindex = idx + 1;
const blockHeight = Math.floor(blockEl.getBoundingClientRect().height);
const blockHalf = blockHeight / 2;
return blockHeight * blockindex - blockHalf;
});
const leftMoveLimitImg = -centerPoints[0] / setImageLimitMovement;
const rightMoveLimitImg = centerPoints[0] / setImageLimitMovement;
const textLimit = centerPoints[0] / setTextLimitMovement;
const changeBackground = () => {
const value = window.scrollY;
titles[0].style.transform = `translateY(-${value * speed}px)`;
// IMAGE BOUNCE
// Move to <==
if (centerPoints[0] > value) {
img.style.transform = `translateX(-${
value * (1 / setImageLimitMovement)
}px)`;
titles[1].style.transform = `translateX( ${
0 + value / setTextLimitMovement
}px)`;
titles[1].style.opacity = value / opacityRange;
return;
}
window.requestAnimationFrame(changeBackground);
};
window.addEventListener("scroll", changeBackground);
return () => window.removeEventListener("scroll", changeBackground);
}, [screen]);
return (
<>
{/* <div style={{height:"100vh"}}></div> */}
{width > 650 && (
<div id="section2">
<main ref={ref}>
<h1 id="title" className="titles">
{posts.Title}
</h1>
<section id="block1" className="blocks"></section>
<figure id="passport">
<img
alt="passport"
src="https://cdn.britannica.com/87/122087-050-1C269E8D/Cover-passport.jpg"
/>
</figure>
<h2 id="text1" className="titles text1">
Random Text 1
</h2>
</main>
</div>
)}
{width < 649 && (
<>
<div style={{ height: "100vh", backgroundColor: "black" }}></div>
</>
)}
{/* Stop Scrolling Animation */}
{/* <div>Content</div> */}
</>
);
}
The problem as identified by #lawrence-witt is because the ref object is not yet set when the useEffect runs the first time.
Here is the codesandbox link https://codesandbox.io/s/infallible-lamarr-usim6
I added some comments as I did a bit of refactor, but please feel free to pick what solves your problem.

How can I re-position a canvas element dynamically with CSS

I've written a piece of code in React and with mouse-click, I position a canvas element, I change 3 style property of the canvas element with mouse-click. When the page refreshes I am getting this canvas elements position from the database and try to change it again according to data coming from the database however it doesnt change any property but if i try to change 1 property it changes, what might be causing this?
Here is the relevant part of my code:
function click(e) {
if(upgrade_status){
if ( material[x_cord + "" + y_cord] == null) {
if(neededmat>totalmats){
//
}else{
setStyleProp('left', x_cord*90+30);
setStyleProp('top', y_cord*120+150);
setStyleProp('backgroundColor', "white");
}
}else{
if(mat ===source){
setStyleProp('left', x_cord*90+30);
setStyleProp('top', y_cord*120+150);
setStyleProp('backgroundColor', "white");
}
if(neededmat>totalmats){
//
}else{
//
}
}else{
//errormessage1
}
}
}else{
//errormessage2
}
}
function Economy() {
ref = useRef();
ref2 = useRef();
ref_yazi = useRef();
const kullanici =useTimes()
console.log("kaƧkere")
setStyleProp = (prop, value) => {
setStyle({
...style,
[prop]: value
})
}
const [style, setStyle] = useState({
position: "absolute",
border: "0px solid red",
zIndex: "3",
width: "35px",
height: "25px",
backgroundColor:"transparent"
});
useInterval(() => {
setCount(count + 1);
}, 1000);
let [count, setCount] = useState(0);
update_details=kullanici.minebuildtime;
if ('undefined' !== typeof(update_details) && update_details !== "0") {
let d = new Date();
let currenttime = d.getTime();
timing_mat='';
timing_upgrade_value = '';
timing_mats_values = update_details.split("-");
[timing_cord_x,timing_cord_y,timing_mat,timing_upgrade_value,timing_time]= timing_mats_values;
let time_difference=timing_time-currenttime;
if(time_difference<0){
if(timing_upgrade_value ==="1"){
database.add(timing_cord_x,timing_cord_y,timing_mat,currentcordsandvalues,db,userUid,timing_time);
}else{
ctx.clearRect(timing_cord_x * 90, timing_cord_y * 120, 90, 120);
database.update(timing_cord_x,timing_cord_y,timing_mat,timing_upgrade_value,currentcordsandvalues,db,userUid,timing_time);
}
ctx_yazi.clearRect(timing_cord_x, timing_cord_y, 100, 30);
if(upgrade_status === false){
setStyleProp("backgroundColor", "transparent");
}
upgrade_status=true;
}else{
ctx_yazi.clearRect(timing_cord_x, timing_cord_y, 100, 30);
if(upgrade_status!==false){
//this is the part that doesnt work properly, only works if i use
//setStyleProp once
setStyleProp('position', 'absolute');
setStyleProp('backgroundColor', 'blue');
setStyleProp('left', timing_cord_x*90+30);
setStyleProp('top', timing_cord_y*120+150);
ctx_yazi.font = "14px Arial";
ctx_yazi.fillStyle = "black";
ctx_yazi.textAlign = "bottom";
}
ctx_yazi.fillText(Math.trunc(time_difference/1000), 5, 17);
upgrade_status=false;
}
}else{
upgrade_status=true;
}
return (
<>
{user ? (
<div className="screens">
<canvas
ref={ref2}
className="buildings"
width={800}
height={120}
onClick={e => {
}}
/>
<canvas
ref={ref}
className="gameScreen"
width={800}
height={600}
onClick={e => {
}}
/>
<canvas
ref={ref_yazi}
style={style}
width={30}
height={25}
/>
</div>
) : (<div>Please login</div>)}
</>
);
}
Okay after a bit struggle i managed get it work by doing a few changes. First of all i realised setStyleProp function under Economy function was changing only the last property of the style so i changed the function setStyleProp to accept 3 variables like below,
setStyleProp = (prop1, value1,prop2,value2,prop3,value3) => {
setStyle({
...style,
[prop1]: value1,
[prop2]: value2,
[prop3]: value3,
})
}
and then i changed the function call under Economy function like this;
setStyleProp('top', timing_cord_y*120+150,'left', timing_cord_x*90+30,'backgroundColor','white');
and it worked just like expected. The weird part i didnt understand i didnt change function calls at onClick events and they still work like below
setStyleProp('left', x_cord*90+30);
setStyleProp('top', y_cord*120+150);
setStyleProp('backgroundColor', "white");

React, make state apply to single element in loop

I have 3 circles that should change src of image when toggled, currently all circles toggle the src when one is clicked. I could use some help with how to get that problem fixed.
This is what i got right now
this.state = {
fillCircle: false
};
circleHandler = () => {
this.setState({ fillCircle: !this.state.fillCircle });
};
render() {
let circles = [];
for (var i = 0; i < 3; i++) {
circles.push(
<img
key={i}
className="circle"
onClick={this.circleHandler.bind()}
src={this.state.fillCircle ? filled_circle : circle}
alt=""
/>
);
}
return (
<div>
{circles}
</div>
);
This is because each of those elements needs it's own state. Write a separate component for each circle. Then you would do
circles.push(<CircleComponent key={index} />)
Inside CircleComponent you would have your state for each Component and toggle for each one of them.
Don't forget about keys as well.
Didn't try it, but you should get the concept.
this.state = {
fillCircle: [false,false,false]
};
circleHandler = (i) => {
this.setState((prev) => {
if(prev.fillCircle[i] == false)prev.fillCircle = [false,false,false] // <--
prev.fillCircle[i] = !prev.fillCircle[i];
return prev.fillCircle;
});
};
render() {
let circles = [];
for (var i = 0; i < 3; i++) {
circles.push(
<img
key={i}
className="circle"
onClick={()=> this.circleHandler(i) }
src={this.state.fillCircle[i] ? filled_circle : circle}
alt=""
/>
);
}
return (
<div>
{circles}
</div>
);
#Dille Please try below code this should solve your problem.
this.state = {
activeCircle: null
};
circleHandler = (i) => {
this.setState({ activeCircle: i});
};
render() {
let circles = [];
for (var i = 0; i < 3; i++) {
circles.push(
<img
key={i}
className="circle"
onClick={this.circleHandler.bind(this, i)}
src={this.state.activeCircle === i ? filled_circle : circle}
alt=""
/>
);
}
return (
<div>
{circles}
</div>
);

Categories