I'm trying to do a loading bar with fixed timeout, says within 5 seconds, the bar should all filled up. I'm able to write the html and css but stuck in the js logic.
function App() {
const [tick, setTick] = useState(0);
useEffect(() => {
const id = setInterval(() => {
setTick(tick => tick + 10); //some calculation is missing
}, 1000);
setTimeout(() => {
clearInterval(id);
}, 5000);
return () => clearInterval(id);
}, []);
return (
<div className="App">
<div
style={{
width: "100%",
background: "yellow",
border: "1px solid"
}}
>
<div
style={{
height: "10px",
background: "black",
width: tick + "%"
}}
/>
</div>
</div>
);
}
https://codesandbox.io/s/proud-architecture-fuwcw
I refactored your code a little.
I created 3 constants:
maxLoad: Is the percentage to cover, in your case a 100%.
fulfillInterval: It's the interval to fill a step in the bar.
step: It's the calculation of the width to fill in the present iteration.
Then I changed a while the code adding 1 milisecond to the clearTimeout to ensure that it's going to work and... it's working. :)
Hope this helps.
import React, { useEffect, useState } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function App() {
const [tick, setTick] = useState(0);
const maxLoad = 100; // total percentage to cover
const fulfillInterval = 5000; // clear interval timeout
const step = maxLoad/(fulfillInterval/1000); // % filled every step
useEffect(() => {
const id = setInterval(() => {
setTick(tick => tick + step); // No dependency anymore
}, 1000);
setTimeout(() => {
clearInterval(id);
}, fulfillInterval+1);
return () => clearInterval(id);
}, []);
return (
<div className="App">
<div
style={{
width: "100%",
background: "yellow",
border: "1px solid"
}}
>
<div
style={{
height: "10px",
background: "black",
width: tick + "%"
}}
/>
</div>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
useEffect(() => {
const id = setInterval(() => {
if(tick !==100)
setTick(tick => tick + 10); // No dependency anymore
}, 1000);
setTimeout(() => {
clearInterval(id);
}, 5000);
return () => clearInterval(id);
}, [tick])
Replace your useEffect function like this.
Related
The following is most likely a fundamental question. I have the following class. The value of this.isRunning is updated to true in start() but after running start() at stop() the value is still false. What is the reason for that? This happens when the class is imported into a React component and instantiated.
The latest version of Stopwatch as used in a React component can be seen below:
import moment from "moment";
export default class Stopwatch {
constructor() {
console.log("run");
this.isRunning = false;
this.lastUpdateTime = 0;
this.totalDuration = 0;
this.interval = null;
}
start() {
if (this.isRunning) {
return console.error("Stopwatch is already started");
}
console.log("Stopwatch started");
this.lastUpdateTime = window.performance.now();
this.isRunning = true;
console.log(this.isRunning);
this.interval = setInterval(() => {
this.totalDuration += window.performance.now() - this.lastUpdateTime;
this.lastUpdateTime = window.performance.now();
}, 100); // update about every 100 ms
}
stop() {
console.log(this.isRunning)
if (!this.isRunning) {
return console.error("Stopwatch is already stopped");
}
console.log("Stopwatch stopped");
console.log("Stopwatch duration:", this.getFormattedDuration());
this.isRunning = false;
clearInterval(this.interval);
}
reset() {
console.log("Stopwatch reset");
this.isRunning = false;
this.lastUpdateTime = 0;
this.totalDuration = 0;
clearInterval(this.interval);
}
formatTime = (ms) => moment(ms).format("mm:ss.S");
getFormattedDuration() {
if (this.lastUpdateTime === 0) {
return "00:00.000";
}
return this.formatTime(this.totalDuration);
}
}
First, the stopwatch is started. Then it is stopped, and finally, reset.
The console output is the following:
Here is a stripped-down version of the original React component where the issue still occurs:
import React from 'react';
import { Button, Row, Col } from 'antd';
import { I18n } from 'aws-amplify';
import Stopwatch from '../../../../../utils/Stopwatch';
export default function TenMeter(props) {
const stopwatch = new Stopwatch();
const startTrial = async (index) => {
stopwatch.start();
}
const completeTrial = async (index) => {
stopwatch.stop();
resetParameters();
}
const resetTrial = async (index) => {
resetParameters();
}
const resetTest = (index) => {
resetParameters();
}
/**
* Reset parameter values.
*/
const resetParameters = () => {
stopwatch.reset();
}
function getTrialsButtonGroup(index) {
return (
<Row key={index} gutter={[16, 16]}>
<Col xs={{ span: 24, offset: 0 }} lg={{ span: 24, offset: 0 }}>
<span>{I18n.get('Trial')} {index + 1}</span>
<Button style={{ marginLeft: "10px" }} onClick={() => startTrial(index)}>{I18n.get('Start')}</Button>
<Button style={{ marginLeft: "10px" }} onClick={() => resetTrial(index)}>{I18n.get('Reset')}</Button>
<Button style={{ marginLeft: "10px" }} onClick={() => completeTrial(index)}>{I18n.get('Stop')}</Button>
</Col>
</Row>
)
};
return (
<div>
{getTrialsButtonGroup(0)}
</div>
);
}
When used in the above React component, Stopwatch has several instances initialized as it can be seen that the constructor is run several times.
In this JSFiddle snippet the constructor runs only once but the same issue occurs when stopwatch.stop() is triggered using the console.
I'm using react-virtuoso library to render a simple virtual list. The code is very straightforward. I pass this overscan props and expect the virtual list to render n items above and below the viewport but it's not working.
The ExpensiveComponents still renders 'loading...' text when I'm scrolling up and down a little. Here is the code:
import { Virtuoso } from "react-virtuoso";
import { useEffect, useState, useRef, PropsWithChildren } from "react";
function ExpensiveComponent({ children }: PropsWithChildren<{}>) {
const [render, setRender] = useState(false);
const mountRef = useRef(false);
useEffect(() => {
mountRef.current = true;
setTimeout(() => {
if (mountRef.current) {
setRender(true);
}
}, 150);
return () => void (mountRef.current = false);
}, []);
return (
<div style={{ height: 150, border: "1px solid pink" }}>
{render ? children : "Loading..."}
</div>
);
}
Usage
function App() {
return (
<Virtuoso
style={{ height: "400px" }}
totalCount={200}
overscan={3} // ----------> this line does not work
itemContent={(index) => {
return <ExpensiveComponent>{index}</ExpensiveComponent>;
}}
/>
);
}
I missed this detail from the docs. Unlike react-window API, the overscan unit is pixel instead of row in virtual list, in my case I need to increase the overscan to 900px and it seems to be working now.
<Virtuoso
style={{ height: "400px" }}
totalCount={200}
overscan={900}
itemContent={(index) => {
return <ExpensiveComponent>{index}</ExpensiveComponent>;
}}
/>
Every element of the array should be displayed for some time and the time for which each element is displayed should be determined by a value in each element.
let array=[{display:"a",time:10},{display:"b",time:15},{display:"c",time:22}]
class App extends React.Component{
state={stateDisplay:"",
stateTime:""
}
componentWillMount(){
var i=0;
let handle=setInterval(()=>{
var element= array[i]
this.setState({
stateDisplay:element.display,
stateTime:element.time,
})
i=i+1;
if(i===array.length){
clearInterval(handle)
}
},10000)
}
render(){
return(
<div> {this.state.stateDisplay} </div>
)}}
i have done something like this but using setinterval the delay can only be set for a constant time,here 10s.
I want the first element to display for 10s and then the next element for 15s, third for 22s which is the time value for each element of the array.
I know i cant do that using setinterval is there a way to do this using Settimeout?
This was almost like a little challenge, heres what i managed to come up with, its in typescript, if you need js, just remove interfaces and type annotations
/* eslint-disable #typescript-eslint/no-explicit-any */
/* eslint-disable prettier/prettier */
/* eslint-disable no-shadow */
/* eslint-disable no-console */
import React, { FC, useState, useEffect, useCallback } from 'react';
import { View, Button, Text } from 'react-native';
interface Data {
duration: number;
bgColor: string;
}
const dataArr: Data[] = [
{ duration: 3, bgColor: 'tomato' },
{ duration: 6, bgColor: 'skyblue' },
{ duration: 9, bgColor: 'gray' },
];
const Parent = () => {
const [currentIdx, setCurrentIdx] = useState<number>(0);
const [elementData, setElementData] = useState<Data>(dataArr[currentIdx]);
useEffect(() => {
console.log('idx', currentIdx);
if (currentIdx > dataArr.length) return;
setElementData({ ...dataArr[currentIdx] });
}, [currentIdx]);
const pushNext = () => {
setCurrentIdx(currentIdx + 1);
};
const handleRestart = () => {
setCurrentIdx(0);
setElementData({ ...dataArr[0] });
};
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Timer
data={elementData}
onCountDownComplete={pushNext}
restart={handleRestart}
/>
</View>
);
};
interface Props {
data: Data;
onCountDownComplete: () => void;
restart: () => void;
}
const Timer: FC<Props> = ({ data, onCountDownComplete, restart }) => {
const [seconds, setSeconds] = useState<number>(data.duration);
// update on data change
useEffect(() => {
setSeconds(data.duration);
}, [data]);
const callback = useCallback(() => {
onCountDownComplete();
}, [onCountDownComplete]);
useEffect(() => {
let interval: any = null;
if (seconds > -1) {
interval = setInterval(() => {
if (seconds - 1 === -1) {
callback();
} else {
setSeconds(seconds - 1);
}
}, 1000);
} else {
return;
}
return () => {
clearInterval(interval);
};
}, [seconds, callback]);
return (
<View
style={{ backgroundColor: data.bgColor, padding: 16, borderRadius: 10 }}
>
<Text style={{ marginBottom: 24 }}>{seconds}</Text>
<Button title="restart" onPress={restart} />
</View>
);
};
Hopefully, the title isn't too ambiguous, but I am struggling how to summarise my issue in just a few words.
What I am attempting to achieve is the one image with appear for 1.5 seconds, the opacity of image will go from 0.3 to 1 and a string of text "Completed" appears below the image, and will then rotate and move to the next image where it will have the same outcome, constantly looping through each image.
The issue is, it only seems to rotate once. It will update the styles and add the text below the image but fail to rotate.
I have a 3d carousel component that rotates the items within an array logos when a function is called rotate().
const [rotateDeg, setRotateDeg] = useState(0);
const [currentIndex, setCurrentIndex] = useState(0);
const rotate = () => {
const maxIndex = logos.length - 1;
const incrementIndex = currentIndex + 1;
const newIndex = incrementIndex > maxIndex ? 0 : incrementIndex;
setCurrentIndex(newIndex);
setRotateDeg(rotateDeg - 360 / logos.length);
};
return (
<Container>
<Carousel ref={carousel} logosLength={logos.length} rotateDeg={rotateDeg}>
{logos.map((item, index) => {
const { key } = item;
return (
<Item
key={key}
index={index}
logosLength={logos.length}
currentIndex={currentIndex}
>
<LoadingSpinLogo
item={item}
delay={1500 * (index + 1)}
rotate={() => rotate()}
key={key}
isCurrent={currentIndex === index}
/>
</Item>
);
})}
</Carousel>
</Container>
);
The component LoadingSpinLogo is where I seem to be having an issue.
I have tried to pass different dependencies into the useEffect callback but it seems to cause weird issues with the delay.
const LoadingSpinLogo = ({ item, rotate, delay, isCurrent }) => {
const [completed, setCompleted] = useState(false);
const [divStyle, setDivStyle] = useState({ opacity: 0.3 });
const props = useSpring(divStyle);
const customStyles = {
height: "78px",
width: "115px",
backgroundRepeat: "no-repeat",
backgroundPosition: "center center",
display: "block",
margin: "auto"
};
const updateCompleted = () => {
setCompleted(true);
setDivStyle({ opacity: 1, from: { opacity: 0.3 } });
rotate();
};
useEffect(() => {
const timeoutID = setTimeout(function() {
updateCompleted();
}, delay);
return () => {
// Clean up the subscription
window.clearInterval(timeoutID);
};
}, []);
return (
<LoadingSpinContainer>
<animated.div style={props}>
<ImageServer png={item.url} customStyles={customStyles} />
</animated.div>
{completed ? "Completed" : null}
</LoadingSpinContainer>
);
};
Here is a CodeSanbox of my components.
Any idea where I am going wrong here?
Any help would be greatly appreciated.
Removing the [] in your useEffect function seems to works fine for me
useEffect(() => {
const timeoutID = setTimeout(function() {
updateCompleted();
}, delay);
return () => {
// Clean up the subscription
window.clearInterval(timeoutID);
};
}, );
I have the following code, which should render a simple fade in animation for the Container component after 3 seconds. However, the component is flashing fully visible before fading in. My question is: why is this happening, and how can I stop it from happening?
import React, { useState, useEffect } from "react";
import { render } from "react-dom";
import posed, { PoseGroup } from "react-pose";
import styled from "styled-components";
const sequence = b =>
b.every(
(a, i) => !(a.call ? a() : setTimeout(() => sequence(b.slice(++i)), a))
);
const usePose = (initial, poses = {}) => {
const [pose, setPose] = useState(initial);
return { pose, setPose, poses };
};
const useAnimation = () => {
const { pose, setPose } = usePose(`hidden`, [`hidden`, `normal`]);
useEffect(() => {
sequence([3000, () => setPose(`normal`)]);
}, []);
return {
pose
};
};
const Container = styled(
posed.div({
hidden: {
opacity: 0
},
normal: { opacity: 1 }
})
)({
color: "red"
});
const App = () => {
const { pose } = useAnimation();
return (
<PoseGroup animateOnMount>
<Container key={0} pose={pose}>
<h1>hello world</h1>
</Container>
</PoseGroup>
);
};
const rootElement = document.getElementById("root");
render(<App />, rootElement);
Issue solved by:
const Container = styled(
posed.div({
hidden: {
opacity: 0
},
normal: { opacity: 1 }
})
)({
color: "red"
opacity: 0, // Add this to stop flash.
});