Render array in React one-by-one, having pauses between - reactjs

I'm having trouble rendering multiple Snackbars, I know it's not allowed to render multiple at once, so I tried notistack,but when I put maxSnack={1} autoHideDuration={3000} and I enqueue 2 Snackbars, then the first one disappears immediately, and I would need to make it stay for those 3 seconds. Therefore I've decided to create own solution. I have an array of messages, and I want to render Snackbars with those messages like render 1st one for 3 seconds, then 2nd one etc.
I've tried to accomplish this with setTimeout() but didn't succeed. I am not sure how to approach this problem. Here's what I got so far:
export const NotificationsInterval = (props: INotificationsProps) => {
const [notifications, setNotifications] = React.useState<IListItem[]>([]);
const [currentIndex, setCurrentIndex] = React.useState<number>(0);
const [currentItem, setCurrentItem] = React.useState<IListItem>();
React.useEffect(() => {
const timer = setTimeout(() => {
setCurrentItem(props.notifications[currentIndex]);
setCurrentIndex(currentIndex => currentIndex + 1);
}, 1000);
return () => clearTimeout(timer);
}, []);
return (
<>
{notifications.length !== 0 && notifications.map(n => {
{currentItem.Title}
})}
</>
);
}

Related

React setState after some seconds when user doesn't continue typing after some seconds without useReff and multiple forms

So I'm in a situation where I can't use useReff because I have a big state where I need to edit the value directly on that state, but I also don't want the state to change and render that specific component because is to heavy and I have lag rendering the component, more exactly I'm using quill-react, and this component is big so It has so lag in input with many renders.
So I have done a function that when the user stops typing after 2 seconds a function is called, and in case it doesn't stop the function set the states to true always so the function doesn't call when is checked, but is not working for some reason, I'm kinda new on this stuff.
Here is what I have done until now, and don't forget I'm iterating a list of inputs and then managing them with setState on another component, using the method: Work.Description:
const [call, setCall] = useState(false);
const [editors, setEditors] = useState({});
const [currentIndex, setCurrentIndex] = useState("");
const handleDescription = (e: any, index: number) => {
setCurrentIndex(index);
setEditors( { ...editors, [index]: e } )
setTimeout(() => setCall(true), 1000)
}
useEffect(() => {
setInterval(() => {
if (call === true) {
Work.Description(props, currentIndex, editors[currentIndex])
setCurrentIndex("")
setCall(false)
}
}, 1000)
}, [])
return (
<Container>
<Items>
{
props.resume.employmentHistory.positions.map(
(position: any, index: number) =>
<Editor
label="Description"
value={position.description && position.description || ''}
onChange={(e) => handleDescription(e, index)}
/>
)
}
</Items>
</Container >
)
As I mentioned, I want something like this not that fancy with useReff or other methods, let's see what we can do.
I think debounce might be what you're looking for, is that correct? Do something when user stops doing something for n milliseconds.
If yes then you probably don't need timeout and interval. I think you forgot to clear those timeouts/intervals by the way!
So you can do it like this (don't forget to install #types/lodash.debounce if you're using TypeScript):
import { useCallback } from 'react'
import debounce from 'lodash.debounce'
// Rename this to fit your needs.
const callWorkDescription = useCallback(
debounce((props, currentIndex, editor) => {
Work.Description(props, currentIndex, editor)
setCurrentIndex("")
}, 2000),
[]
)
const handleDescription = (e: any, index: number) => {
setCurrentIndex(index);
setEditors((prev) => {
const nextEditors = { ...editors, [index]: e }
callWorkDescription(props, index, nextEditors[index])
return nextEditors
})
}

Switching image src with images from an array on an interval in React

This should be fairly simple, but I keep getting a weird behaviour from the result.
Basically, I have an array of images:
const images = [img1, img2, img3, img4, img5, img6];
I also have an image index:
const [imageIndex, setImageIndex] = useState(0);
Then I do a little incrementation of the index:
const switchImage = () => {
if (imageIndex === images.length - 1) {
setImageIndex(0);
} else {
setImageIndex(imageIndex + 1);
}
return imageIndex;
}
Then I call this function from a useEffect:
useEffect(() => {
setInterval(() => {
switchImage();
}, 1000);
}, []);
And finally I add the html:
<img src={images[imageIndex]} />
The result is usually it gets stuck on the second image and stops incrementing, so I thought the issue might be with the useEffect and the way the component is rendering.
You need to use the second method signature of the useState setter function which gives you the previous state value to avoid the stale closure captured value.
const root = ReactDOM.createRoot(document.getElementById('root'));
const images = ['1','2','3','4','5','6'];
const Thing =()=>{
const [imageIndex, setImageIndex] = React.useState(0);
React.useEffect(() => {
setInterval(() => {
setImageIndex(prev => (
prev === images.length - 1 ? 0 : prev + 1
));
}, 1000);
},[])
console.log(imageIndex)
return (
<div>
<h1>{images[imageIndex]}</h1>
</div>
);
}
root.render(<Thing />);
See here https://codepen.io/drGreen/pen/JjpmQrV
Also worth seeing this link which is virtually identical.
In your case the useEffect which you have created it is only being triggered once; when the component is loading - that is because you did not define when this logic should be triggered by adding dependencies to the useEffect.
Now, since the component renders once, 'switchImage'()' is only being triggered once, hence, it iterates once, display the img and stops.
Here is some good documentation on useEffect if you would like to read more about it Using the Effect Hook - React
๐Ÿ’กHere is a slightly altered solution where we are using the debounce technique for the timer. SOLUTION๐Ÿ’ก
const root = ReactDOM.createRoot(document.getElementById('root'));
const images = ['๐Ÿ’ก','๐Ÿ˜Š','๐Ÿ˜','๐Ÿ˜','๐ŸŽฏ','๐Ÿ‘Œ'];
const DemoComponent = () =>{
const [imageIndex, setImageIndex] = React.useState(0);
//debounce set default 0.3s
const debounce = (func, timeout = 300) =>{
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => { func.apply(this, args); }, timeout);
};
}
// switch img fn.
const switchImage = () => {
setImageIndex(imageIndex === images.length - 1 ? 0 : imageIndex + 1)
return imageIndex;
}
//debounce switchImage and set timer to 1s
const switchImageDebounce = debounce(() => switchImage(),1000);
//useEffect
React.useEffect(() => {
switchImageDebounce()
}, [imageIndex]);
return (
<div>
<h1>{images[imageIndex]}</h1>
</div>
);
}
root.render();

React + Typescript: setTimeout in For Loop is not populating array consecutively, or at all

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

Adding seconds to Intl.dateTimeFormat with React useEffect, useState, and setInterval

I have come across a weird problem where changing the order of a clone inside a setState() hook function changes the expected behavior.
I am trying to add one second to the value each second. However doing this directly causes the seconds to increase by two instead of one.
This works
const [value, setValue] = useState(new Date());
useEffect(() => {
const interval = setInterval(
() =>
setValue((value) => {
const clonedDate = new Date(value.getTime());
clonedDate.setSeconds(clonedDate.getSeconds() + 1); // Add one second to the time
return clonedDate;
}),
1000
);
return () => {
clearInterval(interval);
};
}, []);
This adds two seconds instead of one
const [value, setValue] = useState(new Date());
useEffect(() => {
const interval = setInterval(
() =>
setValue((value) => {
value.setSeconds(value.getSeconds() + 1);
const clonedDate = new Date(value.getTime());
return clonedDate;
}),
1000
);
return () => {
clearInterval(interval);
};
}, []);
The only thing that is clear to me as that in the second version a state mutation is obviously occurring, but to be honest it isn't clear to me exactly where. It seems as though even though you are creating a new javascript Date object that it is still referencing properties of the previous data object.
Consider the following examples that exhibit identical behavior:
function App() {
const [value1, setValue1] = useState({ c: 0 });
const [value2, setValue2] = useState({ c: 0 });
useEffect(() => {
const interval = setInterval(
() =>
setValue1((value) => {
const clonedValue = { ...value }; // shallow copy first
clonedValue.c = clonedValue.c + 1; // update copy
return clonedValue; // return copy
}),
1000
);
return () => {
clearInterval(interval);
};
}, []);
useEffect(() => {
const interval = setInterval(
() =>
setValue2((value) => {
value.c = value.c + 1; // mutate current
const clonedValue = { ...value }; // shallow copy
return clonedValue; // return copy
}),
1000
);
return () => {
clearInterval(interval);
};
}, []);
return (
<div className="App">
<h1>Non-mutaiton Version</h1>
{value1.c}
<h1>Mutation Version</h1>
{value2.c}
</div>
);
}
Interestingly though, if you remove the React.StrictMode from around App the two perform identically.
StrictMode currently helps with:
Identifying components with unsafe lifecycles
Warning about legacy string ref API usage
Warning about deprecated findDOMNode usage
Detecting unexpected side effects
Detecting legacy context API
Detecting unexpected side effects
Strict mode canโ€™t automatically detect side effects for you, but it can help you spot them by making them a little more deterministic. This is done by intentionally double-invoking the following functions:
Class component constructor, render, and shouldComponentUpdate methods
Class component static getDerivedStateFromProps method
Function component bodies
State updater functions (the first argument to setState)
Functions passed to useState, useMemo, or useReducer
Demo with your original date objects running in both StrictMode and non-StrictMode:
The issue is with below line it is mutating the value object. You don't need to call value.setSeconds, value.getSeconds()+1 itSelf is enough to increment seconds by 1.
Replace below line of code -
const interval = setInterval(
() =>
setValue2((value2) => {
let second = value2.getSeconds() +1;
//value2.setSeconds(second);
let newTime = new Date();
newTime.setSeconds(second);
const clonedDate = new Date(newTime.getTime());
return clonedDate;
}),
1000
);

React Hooks - Removing component after use

I'm trying to wrap my head around this. My custom hook is supposed to create a simple popup with the desired input and remove after 3 seconds. Of course, currently, it re-renders every time the counter has reset. How can I make it render only once and then be removed from the dom?
export function createPopup(content, popupType) {
const [message, setMessage] = useState(content)
const [type, setType] = useState(popupType)
const [value, setCounter] = useState(3)
const myTimer = setTimeout(() => {
return setCounter(value - 1)
}, 1000)
useLayoutEffect(() => {
const id = setTimeout(() => {
setCounter(value + -1);
}, 1000);
return () => {
clearTimeout(id);
};
}, [value])
return (
<>
{value !== 0 &&
<PopupMolecule type={type}>
{message}
</PopupMolecule>
}
</>
)
}
I think you want something more like this:
export function createPopup(content, popupType) {
const [visible, setVisible] = useState(true);
useEffect(() => {
setTimeout(() => setVisible(false), 3000);
}, []);
return (
<>
{visible &&
<PopupMolecule type={popupType}>
{content}
</PopupMolecule>
}
</>
)
}
There are still some improvements that need to made here (i.e. fading out on exit or some transition, and the way this is setup you can't use it more than once), but this should fix the problem you stated.
This will show your popup for three seconds on mount, then make your popup invisible and unmount it from the DOM. The empty array in the useEffect hook lets it know to only trigger on mount (and unmount if you return a value). You also don't need the other state variables that you're not updating. Those can just be passed in as function parameters.

Resources