Hi I am new developer at ReactJs world. I have a question. I have value variable with initial value as 1. But I have a problem while increasing it. In JavaScript I can incarease an any value one by one but I did not make same thing using Hooks. The thing which I want to do is changing background image with time. Could you help me at this issue? How can I change my background image with time ?
my example tsx.part:
import React, { useEffect, useState } from 'react';
const LeftPart = (props: any) => {
let imgNumber : number = 1;
const [value, setValue] = useState(1);
useEffect(() => {
const interval = setInterval(() => {
imgNumber = imgNumber + 1;
setValue(value+1);
console.log(imgNumber)
console.log(value)
}, 3000);
return () => clearInterval(interval);
}, []);
return (
<div className="col-xl-7 col-lg-7 col-md-7 col-sm col-12">
<img id="image" src={"../../../assets/images/bg"+{value}+".jpg"} style={{ width: "100%", height: "99vh" }} alt="Login Images"></img>
</div >
)
}
export default LeftPart;
Your issue is the useEffect block dependency list (that empty array). When you explicitly set no dependencies, React will call the callback on first render and never again. If you want it to continuously change, just remove that second parameter entirely. If you implicitly leave no useEffect dependencies, it is called on every render.
Fixed:
useEffect(() => {
const interval = setInterval(() => {
imgNumber = imgNumber + 1;
setValue(value+1);
console.log(imgNumber)
console.log(value)
}, 3000);
return () => clearInterval(interval);
});
Related
This ugly code works. Every second viewportHeight is set to the value of window.visualViewport.height
const [viewportHeight, setViewportHeight] = React.useState(0);
React.useEffect(() => {
setInterval(() => {
setViewportHeight(window.visualViewport.height);
}, 1000);
}, []);
However this doesn't work. viewportHeight is set on page load but not when the height changes.
React.useEffect(() => {
setViewportHeight(window.visualViewport.height);
}, [window.visualViewport.height]);
Additional context: I need the page's height in state and I need the virtual keyboard's height to be subtracted from this on Mobile iOS.
You can only use state variables managed by React as dependencies - so a change in window.visualViewport.height will not trigger your effect.
You can instead create a div that spans the whole screen space and use a resize observer to trigger effects when its size changes:
import React from "react";
import useResizeObserver from "use-resize-observer";
const App = () => {
const { ref, width = 0, height = 0 } = useResizeObserver();
const [viewportHeight, setViewportHeight] = React.useState(height);
React.useEffect(() => {
setViewportHeight(window.visualViewport.height);
}, [height]);
return (
<div ref={ref} style={{ width: "100vw", height: "100vh" }}>
// ...
</div>
);
};
This custom hook works:
function useVisualViewportHeight() {
const [viewportHeight, setViewportHeight] = useState(undefined);
useEffect(() => {
function handleResize() {
setViewportHeight(window.visualViewport.height);
}
window.visualViewport.addEventListener('resize', handleResize);
handleResize();
return () => window.visualViewport.removeEventListener('resize', handleResize);
}, []);
return viewportHeight;
}
I have got a dependency imageNo in useEffect() as I want the element to go up when it's being hidden, but scrollIntoView() does not work properly whenever imageNo changes, but it works when clicking a button.
Updated
import React, { useEffect, useRef, useState } from 'react';
const Product = ({ product }) => {
const moveRef = useRef(product.galleryImages.edges.map(() => React.createRef()));
const [imageNo, setImageNo] = useState(0);
useEffect(() => {
const position = moveRef.current[imageNo]?.current.getBoundingClientRect().y;
console.log('imageNo', imageNo); // <<<<----- This is also called whenever scrolling excutes.
if (position > 560) {
moveRef.current[imageNo]?.current.scrollIntoView({
behavior: 'smooth',
block: 'center',
});
}
}, [imageNo]);
const test = () => {
const position = moveRef.current[imageNo]?.current.getBoundingClientRect().y;
if (position > 560) {
moveRef.current[imageNo]?.current.scrollIntoView({
behavior: 'smooth',
block: 'center',
});
}
};
// This changes `imageNo`
const handleScroll = () => {
let id = 0;
console.log('refs.current[id]?.current?.getBoundingClientRect().y', refs.current[id]?.current?.getBoundingClientRect().y);
const temp = imgArr?.find((el, id) => refs.current[id]?.current?.getBoundingClientRect().y >= 78);
if (!temp) id = 0;
else id = temp.id;
if (refs.current[id]?.current?.getBoundingClientRect().y >= 78) {
setImageNo(id);
}
};
useEffect(() => {
window.addEventListener('scroll', handleScroll);
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, []);
return (
<div className="flex flex-row layout-width ">
{/* aside */}
<div className="sticky flex self-start top-[76px] overflow-y-auto !min-w-[110px] max-h-[90vh]">
<div className="">
{product.galleryImages.edges.map((image, i) => {
return (
<div ref={moveRef.current[i]} key={image.node.id}>
<Image />
</div>
);
})}
</div>
</div>
<button onClick={test}>btn</button>
</div>
);
};
export default Product;
Any suggestion will be greatly appreciated.
I couldn't see where the imageNo is coming from?
If it is just a normal javascript variable then it wouldn't trigger re-render even after putting it inside the useEffect's dependencies array.
If you want to make the re-render happen based on imageNo then make a useState variable for imageNo and change it using setter function that useState provides.
Helpful note - read about useState and useEffect hooks in React.
I'm building a function that makes images of random cars animate across the screen, and I want to stagger the population of the "carsLeft" array with a setTimeout..(which I will ultimately randomize the delay time).
everything works until I try to use a setTimeout. With the code below, no cars get are shown. a Console.log shows that the "carsLeft" array does not populate. When I remove the setTimeout all the cars are shown (at once of course). I have tried IIDE still no luck. Been stuck on this one for awhile, Please Help!
function Traffic() {
let carsLeft: any = [];
const generateCarLeft = () => {
for (let i = 0; i < 5; i++) {
setTimeout(() => {
carsLeft.push(
<CarLeft key={i} className="car__left">
<img
src={carListDay[Math.floor(Math.random() * carListDay.length)]}
alt=""
/>
</CarLeft>
);
}, 3000);
}
};
generateCarLeft();
return <div className="traffic__container">{carsLeft}</div>;
}
export default Traffic;
If you want to generate elements through the loop and happen after the component is mounted is using React.useEffect hook, for example
React.useEffect(() => {
generateCarsLeft()
}, [])
and also using the carsLeft as a state will solve the problem.
If do without state and setTimeout it'll work because before first render(mount) all the data is available.
but if you use on first render list is empty and after setTimeout it'll update variable but react does not know that variable is updated you have to use the state to solve this issue.
function Traffic() {
const [carsLeft, setCarsLeft] = useState([]);
const generateCarLeft = () => {
for (let i = 0; i < 5; i++) {
setTimeout(() => {
setCarsLeft((items) => [
...items,
<CarLeft key={i} className="car__left">
<img
src={carListDay[Math.floor(Math.random() * carListDay.length)]}
alt=""
/>
</CarLeft>,
]);
}, 3000);
}
};
generateCarLeft();
return <div className="traffic__container">{carsLeft}</div>;
}
A React component is essentially just a function, so if you declare a variable inside that function it will be re-created each time you call it. If you want any value to persist between calls it has to be saved somewhere else. In React that 'somewhere' is state.
Unless React detects changes to the state it won't even re-render the component.
So as of now your component is called once, 3 seconds later all 5 new elements are added to the array at once, but the component is no re-rendered, because React is not tracking this change. I'll give you an example of how you make it work, but I'd suggest you learn more about React's concepts of state and life cycle.
function Traffic() {
[carsLeft, setCarsLeft] = React.useState([]);
React.useEffect(()=>{
if(carsLeft.length > 4) return;
setTimeout(() => {
setCarsLeft( cl => [...cl,
<CarLeft key={i} className="car__left">
<img
src={carListDay[Math.floor(Math.random() * carListDay.length)]}
alt=""/>]
);
}, 3000);
})
return <div className="traffic__container">{carsLeft}</div>;
}
This is what you are looking for
const list = ["Hello", "how", "are", "you"];
function App() {
const [myList, updateList] = useState([])
useEffect(() =>{
for (let i=0; i<list.length; i++) {
setTimeout(() => {
updateList((prvList) => ([...prvList, list[i]]));
}, 1000 * i) // * i is important
}
}, []);
return (
<div>
{
myList.map(li => (<div key={li}>{li}</div>))
}
</div>
);
}
Even if this did run, it would not run the way you want it to. Because the for loop is extremely fast, you would wait 3s to get 5 cars at once.
You need to get rid of the loop. And you need to wrap all side effects in a useEffect hook, and all persistent variables like carsLeft in a useState hook.
export default function Traffic() {
const [carsLeft, setCarsLeft] = useState<Array<ReactNode>>([]);
const timeout = useRef<number>();
useEffect(() => {
timeout.current = setTimeout(() => {
if (carsLeft.length < 5)
setCarsLeft([
...carsLeft,
<CarLeft key={carsLeft.length} className="car__left">
<img
src={carListDay[Math.floor(Math.random() * carListDay.length)]}
alt=""
/>
</CarLeft>
]);
else clearTimeout(timeout.current);
}, 3000);
return () => {
clearTimeout(timeout.current);
};
}, [carsLeft]);
return <div className="traffic__container">{carsLeft}</div>;
}
Codesandbox demo: https://codesandbox.io/s/naughty-driscoll-6k6u8?file=/src/App.tsx
I have solved all the issues and everything works perfectly. I used #Summer 's approach to remove the for loop and let the useEffect + State changes create its own endless loop. Instead of a setTimeout I used a setInterval method. An issue I had with Summer's solution was using "carsOnTheLeft.length" to generate key values. This caused multiple keys to have the same value and resulted with some issues. To fix this I created a new piece of state "carLeftIndex" to generate a unique key on every re-render. The generated objects are removed from the state array when the animation is complete thanks to React's "onAnimationEnd()' which triggers the "handleRemoveItem" function. Heres my working code, thank you for your answers!
const getRandomNumber = (min: number, max: number) => {
return Math.random() * (max - min) + min;
};
const carListDay = [car_1, car_2, car_3, car_4, car_5, car_6];
function Traffic() {
const [carsOnTheLeft, setCarsOnTheLeft] = useState<any>([]);
const timeout = useRef<any>();
const [carLeftIndex, setCarLeftIndex] = useState(0);
useEffect(() => {
const getRandomNumberToString = (min: number, max: number) => {
const result = Math.random() * (max - min) + min;
return result.toString();
};
setCarLeftIndex(carLeftIndex + 1);
const CarLeft = styled.div`
animation-duration: ${getRandomNumberToString(3, 10)}s;
`;
timeout.current = setInterval(() => {
getRandomNumberToString(2, 9);
if (carsOnTheLeft.length < 15)
setCarsOnTheLeft([
...carsOnTheLeft,
<CarLeft
key={carLeftIndex}
className="car__left"
onAnimationEnd={(e) => handleRemoveItem(e)}
>
<img
src={carListDay[Math.floor(Math.random() * carListDay.length)]}
alt=""
/>
</CarLeft>,
]);
else clearTimeout(timeout.current);
}, getRandomNumber(100, 5000));
console.log(carsOnTheLeft);
console.log(carLeftIndex);
const handleRemoveItem = (e: any) => {
setCarsOnTheLeft((carsOnTheLeft: any) =>
carsOnTheLeft.filter((key: any, i: any) => i !== 0)
);
};
return () => {
clearTimeout(timeout.current);
};
}, [carsOnTheLeft]);
return <div className="traffic__container">{carsOnTheLeft}</div>;
I would expect this useEffect to fail on the first render, since I would assume the innerCarouselRef.current would be undefined on the first render and it makes a call to getBoundingClientRect. Why does it work/why is the innerCarouselRef.current defined when the useEffect runs?
import React from 'react';
import { debounce } from 'lodash';
export default function Carousel({ RenderComponent }) {
const [innerCarouselWidth, setInnerCarouselWidth] = React.useState(0);
const [itemWidth, setItemWidth] = React.useState(0);
const innerCarouselRef = useRef();
const itemRef = useRef();
const content = data.map((el, i) => {
return (
<div key={`item-${i}`} ref={i === 0 ? itemRef : undefined}>
<RenderComponent {...el} />
</div>
);
});
useEffect(() => {
const getElementWidths = () => {
setInnerCarouselWidth(innerCarouselRef.current.getBoundingClientRect().width); // why doesn't this call to getBoundingClientRect() break?
setItemWidth(itemRef.current.getBoundingClientRect().width);
};
getElementWidths();
const debouncedListener = debounce(getElementWidths, 500);
window.addEventListener('resize', debouncedListener);
return () => window.removeEventListener('resize', debouncedListener);
}, []);
return (
<div className="inner-carousel" ref={innerCarouselRef}>
{content}
</div>
)
}
React runs the effects after it has updated the DOM (we typically want it to work that way). In your case, the effect runs after the component has mounted and so innerCarouselRef.current is set.
I would recommend reading the useEffect docs to gain a better understanding.
Hi I am new developer at ReactJs. I have a problem about useEffect rendering. I have an example and i am trying to change background image with time but useEffect make rendering so much and my background image is staying in a loop with time. I just want to change my images with order like bg1 bg2 bg3 etc.
How can I solve this infinite render?
my example .tsx
import React, { useEffect, useState } from 'react';
import { connect } from "../../../store/store";
const LeftPart = (props: any) => {
const [value, setValue] = useState(1);
const {loginLeftImagesLength} = props;
const changeLeftBackgroungImage : any = () =>{
const interval = setInterval(() => {
if (value <= loginLeftImagesLength.payload) {
setValue(value+1);
} else{
setValue(1);
}
}, 3000);
return () => clearInterval(interval);
};
useEffect(() => {
changeLeftBackgroungImage();
});
return (
<div className="col-xl-7 col-lg-7 col-md-7 col-sm col-12">
<img id="image" src={"../../../assets/images/bg"+value+".jpg"} style={{ width: "100%", height: "99vh" }} alt="Login Images"></img>
</div >
)
}
export default connect((store: any) => ({ loginLeftImagesLength: store.loginLeftImagesLength }))(LeftPart) as any;
You have infinite render as you have not specified any dependencies in your useEffect
useEffect(() => {
changeLeftBackgroungImage();
},[]); // Will run this hook on component mount.
Also you could do it in this way
useEffect(()=>{
const timer = setTimeout(()=>{
if (value <= loginLeftImagesLength.payload) {
setValue(value+1);
} else{
setValue(1);
}
},3000)
return ()=>{ // Return this function from hook which is called before the hook runs next time
clearTimeout(timer)
}
},[,value]) // RUN THIS HOOK ON componendDidMount and whenever `value` changes
Why not put this entire code inside the useEffect hook.
useEffect(() => ) {
const {loginLeftImagesLength} = props;
const changeLeftBackgroungImage : any = () =>{
const interval = setInterval(() => {
if (value <= loginLeftImagesLength.payload) {
setValue(value+1);
} else{
setValue(1);
}
}, 3000);
return () => clearInterval(interval);
};[changeBGStateHere])
use if else statements to change the background...
if (value === 1) {
changeLeftBackgroungImage();
} else (value === 2) {
and so on.
Let the interval change the state and let useEffect rerender when the state for the time changes.