Loop through array with setInterval play button and pause it - javascript

I want to loop through a array with a pause and a start button. Right now it automatically plays and I am not sure how to connect the setInterval to the play function and not sure how to stop the interval as everything I try just crashes it. Maybe there is a good a library for doing this as I can imagine this is quite a normal thing to do? If not please help me fix my code, maybe there is better ways to do this if so please tell :)
Codesandbox attempt: https://codesandbox.io/s/modern-dream-4kldkg?file=/src/App.js
import "./styles.css";
import { useState } from "react";
export default function App() {
const array = [1, 2, 3, 5, 6, 7];
const [current, setCurrent] = useState(0);
const play = () => {
setCurrent(current + 1);
};
const pause = () => {};
const reset = () => {
setCurrent(0);
};
var intervalID = setInterval(function () {
if (current >= array.length) {
setCurrent(0);
}
setCurrent(current + 1);
}, 5000);
return (
<div className="App">
<button onClick={() => play()}>play</button>
<br></br>
<button onClick={() => pause()} style={{ marginTop: "5px" }}>
Pause
</button>
<br></br>
<button onClick={() => reset()} style={{ marginTop: "5px" }}>
Reset
</button>
<br></br>
<h1>{array[current]}</h1>
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
</div>
);
}

You should remove the interval on each render, please see the demo:
import "./styles.css";
import { useState, useEffect } from "react";
export default function App() {
const array = [1, 2, 3, 4, 5, 6, 7];
const [current, setCurrent] = useState(0);
const [paused, setPaused] = useState(true);
const play = () => {
setPaused(false);
};
const resume = () => {
setPaused(false);
};
const pause = () => {
setPaused(true);
};
const reset = () => {
setCurrent(0);
setPaused(true);
};
useEffect(
function () {
var timeout;
if (!paused) {
timeout = setTimeout(function () {
const next = current < array.length - 1 ? current + 1 : 0;
setCurrent(next);
}, 1000);
}
return function () {
clearTimeout(timeout);
};
},
[paused, current, array.length]
);
return (
<div className="App">
<button onClick={() => play()}>play</button>
<br></br>
<button onClick={() => pause()} style={{ marginTop: "5px" }}>
Pause
</button>
<button onClick={() => resume()} style={{ marginTop: "5px" }}>
Resume
</button>
<br></br>
<button onClick={() => reset()} style={{ marginTop: "5px" }}>
Reset
</button>
<br></br>
<h1>{array[current]}</h1>
</div>
);
}
https://codesandbox.io/s/throbbing-cdn-4c77nr

Related

Reverse order of JavaScript code - react native

I am trying to count the number of times a button is pressed within a second.
It's working, for the most part, it tracks it and outputs it.
But the problem is that it outputs the button press count from the last second instead of the current second.
I think it would work if the order was reversed somehow, how do I fix up this function? Thanks.
const [clicksPerSecond, setClicksPerSecond] = useState(0);
const [tempCounter, setTempCounter] = useState(0);
const [tempCounter2, setTempCounter2] = useState(0);
const { setCounter, counter } = useContext(CountContext);
useEffect(() => {
console.log(tempCounter);
if (tempCounter != 0) {
if (tempCounter == 1) {
setTimeout(() => {
setClicksPerSecond(tempCounter2);
setClicksPerMinute(tempCounter2 * 60);
setTempCounter(1);
console.log('Clicks per second final: ' + tempCounter2);
}, 1000)
} else {
setTempCounter2(tempCounter);
console.log('Clicks per second: ' + tempCounter);
}
}
setTempCounter(tempCounter + 1);
}, [counter])
return (
<View style={[styles.container, { backgroundColor: colors.background }]}>
<View elevation={7.5} style={{ backgroundColor: colors.background, borderRadius: 500 }}>
<TouchableOpacity
onPress={() => setCounter(counter + 1)}
style={[styles.counterButton, { backgroundColor: colors.primary, borderColor: colors.container, borderWidth: 0 }]}>
<Text></Text>
<Text style={{ fontSize: 60 }}>{counter}</Text>
<Text>{clicksPerSecond} CPS</Text>
</TouchableOpacity>
</View>
</View>
);
}
You can instead just increment the count and decrement it after a second.
export default function App() {
const [clicks, setClicks] = React.useState(0);
const onClick = () => {
setClicks((c) => c + 1);
setTimeout(() => setClicks((c) => c - 1), 1000);
};
return (
<div>
<button onClick={onClick}>Click me</button>
<p>Clicked {clicks} times</p>
</div>
);
}
You will also need to track if the component is unmounted before decrement, which I think you can do using refs. Example

Add a success notification for an action

The site has a button to remove the device from the shopping cart.
The principle of the button is as follows:
the user clicks the delete button;
a window opens (something like a modal window made using Dialog mui) with a warning about the danger of deletion and two buttons: cancel and confirm;
2a) when you click the cancel button, the window closes;
2b), when the confirmation button is pressed, the deletion process begins, which is accompanied by a scroll wheel. After deletion, the window closes and the user can continue working on the site.
I would like, after closing the window, to display a notification for a few seconds that the item was successfully deleted. The whole difficulty lies in the fact that there is no fixed deletion time (the deletion time is different depending on the amount of information about the device) and it is not clear to me when the notification window should be called.
Help to understand please.
Here is my short working code
export function Delete() {
const [alertShown, setAlertShown] = useState(false);
const [alertLoading, setAlertLoading] = useState(false);
const onNo = () => {
setAlertShown(false);
};
const onYes = async () => {
setAlertLoading(true);
await deleteItem();
setAlertShown(false);
setAlertLoading(false);
};
return <ButtonGroup >
<div onClick={() => setAlertShown(true)}>
<DeleteForeverIcon/>
</div>
{alertShown && (
<Dialog open={onYes}>
{alertLoading
? <div ><Spinner/></div>
: <DialogActions >
<Button onClick={onNo}>Cancel</Button >
<Button onClick={onYes}>Confirm </Button >
</DialogActions>}
</Dialog>
)}
</ButtonGroup>
}
Here, for a better understanding, I demonstrate a demo version of what I have going on at the moment. The code in the codeSandbox is slightly different from the code I showed above. But the principle is the same. I will be grateful for any help
I just added some basic things on what you can do here:
import { useState } from "react";
import "./styles.css";
const sleep = (ms = 1000) => new Promise((resolve) => setTimeout(resolve, ms));
function randomIntFromInterval(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
async function deleteItem(item, firestore, urlParams) {
await deleteRecord(firestore, item.id);
}
async function deleteRecord(firestore, recordId) {
await firestore.collection("records").doc(recordId).delete();
}
const firestoreDummy = {
collection: () => ({
doc: () => ({
delete: async () => {
// Simulating delete action on firestore
await sleep(randomIntFromInterval(3000, 10000));
}
})
})
};
const Dialog = ({ loading, onYes, onNo }) => {
return (
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
display: "flex",
justifyContent: "center",
alignItems: "center",
backgroundColor: "rgba(0, 0, 0, 0.6)"
}}
>
<div style={{ backgroundColor: "white", padding: 16 }}>
<h4>Are you sure</h4>
<p>Do you really want to delete this item?</p>
{loading && (
<div style={{ marginBottom: "8px" }}>
<span style={{ fontSize: "12px", color: "gray" }}>
Item is being deleted. Please wait...
</span>
</div>
)}
<button disabled={loading} onClick={onYes}>
Yes
</button>
<button disabled={loading} onClick={onNo}>
No
</button>
</div>
</div>
);
};
const SuccessDialog = ({ setSuccessAlertShown }) => {
return (
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
display: "flex",
justifyContent: "center",
alignItems: "center",
backgroundColor: "rgba(0, 0, 0, 0.6)"
}}
>
<div style={{ backgroundColor: "white", padding: 16 }}>
<h4>Sucessfully deleted!</h4>
<button onClick={() => setSuccessAlertShown(false)}>Close</button>
</div>
</div>
);
};
export default function App() {
const [alertShown, setAlertShown] = useState(false);
const [alertLoading, setAlertLoading] = useState(false);
const [alertSucess, setSuccessAlertShown] = useState(false);
const onNo = () => {
setAlertShown(false);
};
const onYes = async () => {
setAlertLoading(true);
try {
await deleteItem({}, firestoreDummy);
setAlertShown(false);
} finally {
setAlertLoading(false);
// A simple notification
alert("The record deleted!");
// A more classic notification
setSuccessAlertShown(true);
}
};
return (
<div className="App">
<button onClick={() => setAlertShown(true)}>Delete item</button>
{alertShown && (
<Dialog onNo={onNo} onYes={onYes} loading={alertLoading} />
)}
{alertSucess && (
<SuccessDialog setSuccessAlertShown={setSuccessAlertShown} />
)}
</div>
);
}
This is the sandbox: https://codesandbox.io/s/nostalgic-hill-r8sh1x

it have Warning which "Cannot update a component while rendering to a different component" after finished the auto count down function

I created a <Backdrop /> component and called in <App />
In <App />, having a button to call out the <Backdrop />. Inside of the <Backdrop />, it has a useEffect to handle the count down function and render the count. After the count is down to 0, the <Backdrop /> will close.
In fact, the modal can close when the count is down to 0, but it will have a warning that Warning: Cannot update a component (`App`) while rendering a different component (`Backdrop`).
const [modal, setModal] = useState(false);
return{
<View>
<Button onPress={() => setModal(true)}/>
<Backdrop modal={modal} callback={() => setModal(false)}/>
</View>
}
const [count, setCount] = useState(0);
useEffect(() => {
if (modal) {
setCount(3);
const tmp = setInterval(() => {
setCount(prev => {
let newCount = --prev;
if (newCount === 0) {
callback();
clearInterval(tmp);
}
return newCount;
});
}, 1000);
}
}, [modal]);
return{
<Modal>
<Text>{count}</Text>
</Modal>
}
Don't update multiple states inside the useState updater function. Track the count in another useEffect, and close the modal when it reaches 0:
const { useState, useEffect } = React;
const Parent = () => {
const [modal, setModal] = useState(false);
return (
<div>
<button onClick={() => setModal(true)}>Show modal</button>
{modal && <Backdrop onClose={() => setModal(false)} />}
</div>
);
}
const Backdrop = ({ onClose }) => {
const [count, setCount] = useState(3);
useEffect(() => {
const interval = setInterval(() => {
setCount(prev => prev - 1);
}, 1000);
return () => {
clearInterval(interval);
};
}, []);
useEffect(() => {
if (count === 0) onClose();
});
return <div>{count}</div>;
};
ReactDOM
.createRoot(root)
.render(<Parent />);
<script crossorigin src="https://unpkg.com/react#18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.development.js"></script>
<div id="root"></div>

how to make an infinite image carousel with hooks in react native

I am making an infinite image carousel using hooks and javascript in react native.
I am almost done with the code and also the carousel works absolutely fine but the active dots below the carousel runs faster than the images. I have tried a couple of things but unfortunately it didn't worked out.
Thanks in advance.
import React, {
useEffect,
useState,
useRef,
useCallback,
createRef,
} from 'react';
import {
StyleSheet,
View,
Dimensions,
FlatList,
LayoutAnimation,
UIManager,
Text,
} from 'react-native';
import {ActivityIndicator} from 'react-native';
import {Image} from 'react-native-elements';
const HomeCarousel = ({data}) => {
const [dimension, setDimension] = useState(Dimensions.get('window'));
const [index, setIndex] = useState(0);
const [dataState, setDataState] = useState(data);
slider = createRef();
let intervalId = null;
const onChange = () => {
setDimension(Dimensions.get('window'));
};
useEffect(() => {
Dimensions.addEventListener('change', onChange);
return () => {
Dimensions.removeEventListener('change', onChange);
};
});
useEffect(() => {
if (Platform.OS === 'android') {
UIManager.setLayoutAnimationEnabledExperimental(true);
}
}, []);
viewabilityConfig = {
viewAreaCoveragePercentThreshold: 50,
};
const onViewableItemsChanged = ({viewableItems, changed}) => {
if (viewableItems.length > 0) {
let currentIndex = viewableItems[0].index;
if (currentIndex % data.length === data.length - 1) {
setIndex(currentIndex),
setDataState(dataState => [...dataState, ...data]);
} else {
console.log(currentIndex, 'else');
setIndex(currentIndex);
}
}
};
const onSlideChange = () => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeIn);
const newIndex = index + 1;
setIndex(newIndex);
slider?.current?.scrollToIndex({
index: index,
animated: true,
});
};
const startInterval = useCallback(() => {
intervalId = setInterval(onSlideChange, 3000);
}, [onSlideChange]);
useEffect(() => {
startInterval();
return () => {
clearInterval(intervalId);
};
}, [onSlideChange]);
const viewabilityConfigCallbackPairs = useRef([
{viewabilityConfig, onViewableItemsChanged},
]);
const renderIndicator = ()=>{
const indicators = [];
data.map((val,key)=>(
indicators.push(
<Text
key={key}
style={
key === index % data.length ? {color: 'lightblue',fontSize:10,marginBottom: 8,marginHorizontal:1} :
{color: '#888',fontSize:10,marginBottom: 8,marginHorizontal:1}
}>
⬤
</Text>
)
));
return indicators;
}
return (
<View style={{width: dimension.width,height: 280, backgroundColor: '#fff'}}>
<FlatList
ref={slider}
horizontal
pagingEnabled
snapToInterval={dimension?.width}
decelerationRate="fast"
bounces={false}
showsHorizontalScrollIndicator={false}
data={dataState}
renderItem={({item, index}) => (
<>
<View>
<Image
source={{uri: `${item.url}`}}
style={{
width: dimension?.width,
height: 250,
resizeMode: 'cover',
}}
PlaceholderContent={<ActivityIndicator />}
/>
</View>
</>
)}
viewabilityConfigCallbackPairs={viewabilityConfigCallbackPairs.current}
getItemLayout={(data, index) => ({
length: dimension?.width,
offset: dimension?.width * index,
index,
})}
windowSize={1}
initialNumToRender={1}
maxToRenderPerBatch={1}
removeClippedSubviews={true}
/>
<View
style={{
flexDirection: 'row',
position: 'absolute',
bottom: 0,
alignSelf: 'center',
}}>
{renderIndicator()}
</View>
</View>
);
};
const styles = StyleSheet.create({});
export default HomeCarousel;
The data that is passed as the props to this component is
export const carouselImages = [
{url: 'https://i.ibb.co/FDwNR9d/img1.jpg'},
{url: 'https://i.ibb.co/7G5qqGY/1.jpg'},
{url: 'https://i.ibb.co/Jx7xqf4/pexels-august-de-richelieu-4427816.jpg'},
{url: 'https://i.ibb.co/GV08J9f/pexels-pixabay-267202.jpg'},
{url: 'https://i.ibb.co/sK92ZhC/pexels-karolina-grabowska-4210860.jpg'},
];
Ooooooh, I fixed it myself
Here is the perfectly working code full code. 😄
import React, {
useEffect,
useState,
useRef,
useCallback,
createRef,
} from 'react';
import {
StyleSheet,
View,
Dimensions,
FlatList,
LayoutAnimation,
UIManager,
Text,
} from 'react-native';
import {ActivityIndicator} from 'react-native';
import {Image} from 'react-native-elements';
const HomeCarousel = ({data}) => {
const [dimension, setDimension] = useState(Dimensions.get('window'));
const [index, setIndex] = useState(0);
const [dataState, setDataState] = useState(data);
const [indicatorIndex, setindicatorIndex] = useState();
slider = createRef();
let intervalId = null;
const onChange = () => {
setDimension(Dimensions.get('window'));
};
useEffect(() => {
Dimensions.addEventListener('change', onChange);
return () => {
Dimensions.removeEventListener('change', onChange);
};
});
useEffect(() => {
if (Platform.OS === 'android') {
UIManager.setLayoutAnimationEnabledExperimental(true);
}
}, []);
viewabilityConfig = {
viewAreaCoveragePercentThreshold: 50,
};
const onViewableItemsChanged = ({viewableItems, changed}) => {
if (viewableItems.length > 0) {
let currentIndex = viewableItems[0].index;
if (currentIndex % data.length === data.length - 1) {
setIndex(currentIndex), setindicatorIndex(currentIndex);
setDataState(dataState => [...dataState, ...data]);
} else {
console.log(currentIndex, 'else');
setIndex(currentIndex);
setindicatorIndex(currentIndex);
}
}
};
const onSlideChange = () => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeIn);
const newIndex = index + 1;
setIndex(newIndex);
slider?.current?.scrollToIndex({
index: index,
animated: true,
});
};
const startInterval = useCallback(() => {
intervalId = setInterval(onSlideChange, 3000);
}, [onSlideChange]);
useEffect(() => {
startInterval();
return () => {
clearInterval(intervalId);
};
}, [onSlideChange]);
const viewabilityConfigCallbackPairs = useRef([
{viewabilityConfig, onViewableItemsChanged},
]);
const renderIndicator = () => {
const indicators = [];
data.map((val, key) =>
indicators.push(
<Text
key={key}
style={
key === indicatorIndex % data.length
? {
color: 'lightblue',
fontSize: 10,
marginBottom: 8,
marginHorizontal: 1,
}
: {
color: '#888',
fontSize: 10,
marginBottom: 8,
marginHorizontal: 1,
}
}>
⬤
</Text>,
),
);
return indicators;
};
return (
<View
style={{width: dimension.width, height: 280, backgroundColor: '#fff'}}>
<FlatList
ref={slider}
horizontal
pagingEnabled
snapToInterval={dimension?.width}
decelerationRate="fast"
bounces={false}
showsHorizontalScrollIndicator={false}
data={dataState}
renderItem={({item, index}) => (
<>
<View>
<Image
source={{uri: `${item.url}`}}
style={{
width: dimension?.width,
height: 250,
resizeMode: 'cover',
}}
PlaceholderContent={<ActivityIndicator />}
/>
</View>
</>
)}
viewabilityConfigCallbackPairs={viewabilityConfigCallbackPairs.current}
getItemLayout={(data, index) => ({
length: dimension?.width,
offset: dimension?.width * index,
index,
})}
windowSize={1}
initialNumToRender={1}
maxToRenderPerBatch={1}
removeClippedSubviews={true}
/>
<View
style={{
flexDirection: 'row',
position: 'absolute',
bottom: 0,
alignSelf: 'center',
}}>
{renderIndicator()}
</View>
</View>
);
};
const styles = StyleSheet.create({});
export default HomeCarousel;

react-countdown resets when rendered in ReactJs

I am creating a game which user can tap a button as much as they can in a range of time.
I have use react-countdown to create Timer. But when I tap to a button, timer is auto reset.
Here my code.
import React, { useState } from "react"
import Countdown from 'react-countdown';
function App() {
const [name, setName] = useState("")
const [isLogin, setIsLogin] = useState(false)
const [counter, setCounter] = useState(0)
const submitName = () => {
setIsLogin(true)
}
const updateCounter = () => {
console.log(counter)
setCounter(counter + 1)
}
return (
<div>
{!isLogin ? (
<div>
<input
style={{ width: 300, height: 30 }}
placeholder="Input name here"
value={name}
onChange={e => setName(e.target.value)}
/>
<button
style={{ width: 50, height: 20, background: "red" }}
onClick={() => submitName()}
>Go</button>
</div>
) : (
<div
style={{ width: "100vw", height: "100vh", background: "yellow" }}>
<Countdown date={Date.now() + 100000} />
<h1>{name}</h1>
<h3>Click counter: {counter} </h3>
<button
style={{
width: 100,
height: 50,
background: "red",
border: "none",
borderRadius: 25,
color: "white",
cursor: "pointer"
}}
onClick={() => updateCounter()}>Click here!</button>
</div>
)
}
</div>
);
}
export default App;
The problem is when I tap the button, setCounter(counter + 1) is running and rerender the page. That made the Counter reset.
I know why it have issue but I cannot fix it.
You need to memoize the Countdown component:
const CountdownWrapper = () => <Countdown date={Date.now() + 100000} />;
const MemoCountdown = React.memo(CountdownWrapper);
// usage
<MemoCountdown/>
Beforehand, on every render Date.now() gets a new value which resets the timer.
import React from 'react';
const time = React.useMemo(() => {
return Date.now() + 120 * 1000;
}, []);

Categories

Resources