I'm making a progress bar, which should receive progress status from method submitAction, in which value for progress bar constantly updating. Here my code:
1.Parent component
<template>
<div>
<progressbar-component :value="progressState"></progressbar-component>
</div>
</template>
<script>
import ProgressBar from './Progress.vue'
export default {
components: {
'progressbar-component': ProgressBar
},
data () {
return {
...
progress: 0
...
}
},
computed: {
...
progressState () {
return this.progress
}
...
},
methods: {
...
submitAction: function (event) {
...
let percent = 0
setInterval(function () {
if(someState > 0) {
this.progress = percent % 100
percent += 10
}
}, 200)
...
}
}
}
</script>
2. Child (progress bar) component
<template>
<div class="progress">
{{ value }}
</div>
</template>
<script>
export default {
name: 'progressbar-component',
props: {
value: {
type: Number,
default: 0
}
}
}
</script>
Aim:
Updating value in Progress Bar's component, while setInterval is running.
Problem:
value doesn't update in child component.
P.S.
Some parts of initial code are just left out to simplify problem representation:
this.progress value changes correctly in parent, and I can track it
through debugger progress bar component also works correctly and
initial value of progress (i.e. 0) passed fine.
Well, this took me some time. Classic mistake. The problem is you don't really change components' progress ever:
submitAction: function (event) {
let percent = 0
setInterval(function () {
if(someState > 0) {
this.progress = percent % 100 // <---- `this` here doesn't refer to the component
percent += 10
}
}, 200)
}
to make it work do:
submitAction: function (event) {
let percent = 0
setInterval(() => { // <---- arrow function doesn't have their own `this`, so `this.progress` will refer to the components' value
if(someState > 0) {
this.progress = percent % 100
percent += 10
}
}, 200)
}
Related
I am changing the background-color of my navbar on scroll event, it works, but the problem is that the function changeNavBar is triggered on every scroll movement. I'd like to know how can I make it be triggered only at a certain point of the screen.
For instance, let's say the function should be called only when the YPosition is bigger than 50, once it is called it wouldn't be called again, unless the screen is scrolled up and the navbar reaches a point smaller than 50. In other words, we start at 0, it reaches 50 it is called, but it isn't called if the screen keeps being scrolled down, it won't be called it YPosition is 100, 200, 1000, doesn't matter. But if the page is scrolled up and reaches 50'inch it will be called.
This is my code:
const Navigation = props => {
let [navBg, setNavBg] = React.useState('transparent')
let navbarStyle = {
backgroundColor: navBg
}
const changeNavBar = () => {
if (window.scrollY > 50) {
setNavBg('blue')
} else {
setNavBg('transparent')
}
console.log('test')
}
React.useEffect(() => {
window.addEventListener('scroll', changeNavBar);
return () =>
window.removeEventListener('scroll', changeNavBar);
}, []);
return (
<div style={navbarStyle}>
<Logo />
<MenuNavigation />
</div>
)
}
The issue is not what happens within the function changeNavBar. What I want is to prevent the function to be called when it is not necessary.
Instead of performing state updates every time, check if it needs to be changed first.
const changeNavBar = () => {
if (window.scrollY > 50) {
if(navBg !== 'blue') setNavBg('blue')
} else {
if(navBg !== 'transparent') setNavBg('transparent')
}
}
Is there anyway to call a method on vue after the viewer scrolled certain amount of page percentage?
For example, i would like to run a method to display an offer after the viewer has scrolled 80% of the page from top to bottom.
<script>
export default {
mounted() {
window.addEventListener("scroll", this.handleScroll);
},
destroyed() {
window.removeEventListener("scroll", this.handleScroll);
},
methods: {
handleScroll(event) {
// Any code to be executed when the window is scrolled
const offsetTop = window.scrollY || 0;
const percentage = (offsetTop * 100) / document.body.scrollHeight;
// Do something with the percentage
},
},
};
</script>
Note If you want to do something ( for example task() ) with a condition that the percentage is equal or greater than some value you must considering a data variable container that how many times that condition is true and do the operation just one time after that.
<script>
export default {
data() {
return {
reached: false, // checker container
};
},
methods: {
task() {
console.log("Triggered just one time >= 80");
},
handleScroll(event) {
// ... After calculating the percentage ...
if (percentage >= 80) {
if (!this.reached) {
this.task();
this.reached = true;
}
} else this.reached = false;
},
},
};
</script>
Live Demo
I'm currently using
import { Progress } from 'react-sweet-progress';
and before I also tried import { Progress } from 'reactstrap'; which just uses bootstrap 4 ProgressBar.
I have a state that maintains the progressValue, and using audio HTML tag to call in an online audio src and playing it. During timeupdate eventListener, I update my progressValue in my state, and reflect it back by setting the value of <Progress> as this.state.progressValue.
class FooBar extends Component {
state = {
progressValue: 0
}
handleProgress = () => {
this.currentTimeInterval = null;
this.audio.onplay = () => {
this.setState({
progressValue: 0,
play: true
});
this.audio.addEventListener('timeupdate', () => {
this.setState({
progressValue: Math.floor(this.audio.currentTime / this.audio.duration * 100)
}, function() {
console.log(this.state.progressValue);
})
}, true)
this.audio.onpause = () => {
clearInterval(this.currentTimeInterval);
}
}
render() {
return(
<audio
ref={(audio) => { this.audio = audio }}
src={http://www.music.helsinki.fi/tmt/opetus/uusmedia/esim/a2002011001-e02.wav}
autoPlay
onLoadedData={this.handleProgress}
/>
<Progress value={this.state.progressValue} />
);
}
}
The timing, however, doesn't seem to match up where the audio will be playing and the progressValue will be delayed in the sense that audio will be ahead of the progressValue. Hence, by the time audio finishes, it would likely take another 2~3 seconds for the progressBar to reach 100%.
I also tried:
this.currentTimeInterval = setInterval(() => {
this.setState({
progressValue: Math.floor(this.audio.currentTime / this.audio.duration * 100)
}))
}, 100)
and tried manipulating the timeInterval of 100ms to a smaller number, which makes it closer but the delay still exists.
3 Questions:
1) What is causing this to happen?
2) Is there a way to fix this?
3) If the answer is 'no' to 2), is there another component I can use to display the progress of an audio file? (Not the default controls from audio tag).
Thank you!
in my page , I have a real-time chart which updates every 3 seconds
I used setInterval(function(){...} , 3000) for make the chart updates.
but my problem is when I move to another page(by javascript) every thing are destroyed except my interval , so when I back to the chart page , it load every thing again and setInterval method works twice on every 3 seconds which makes duplicated points on mu chart.
this is destroy method
every line works except the myInterval one
destroy()
{ this.num=0;
this.c=0;
this.startLive = false;
clearInterval(this.myInterval); }
my problem appears just when I go to another page then back.
<template>
....
</template>
<script>
var charts = [];
export default {
data() {
return {
startLive: false,
num: 0,
c: 0,
myInterval: null,
}
},
methods: {
initChart(dataProvieded) {
charts[this.num] = AmCharts.makeChart("chart" + this.num, {...});
},
loadInitalData(limit) {
this.fetchDatafromServer(limit).then((response) => { ...
this.initChart(data);
this.num++;
this.setInt();
});
},
setInt() {
this.myInterval = setInterval(function() { .... } , 3000);
},
}
destroy() {
this.num = 0;
this.c = 0;
this.startLive = false;
clearInterval(this.myInterval);
}
</script>
It's strange, try to set debugger; before clearInterval to check variables.
By the way not all codepaths ok (looks like initializations doubled).
You should rewrite as
if (this.myInterval !== null) {
clearInterval(this.myInterval);
this.myInterval = null;
}
and add corresponding guard at setInt:
setInt() {
if (this.myInterval === null ) {
this.myInterval = setInterval(function() { .... } , 3000);
}
}
May you need one interval per graph, please check your logic.
I've hit a wall as I start my adventure with React.js. I've got the UI of the following time tracking app working on several levels:
http://jsfiddle.net/technotarek/4n8n17tr/
What's working as hoped:
Filtering based on user input
Project clocks can be started and stopped independently
What's not working:
If you start one or more clocks and then try to filter, any clock that's not in the filter result set gets reset once it is re-displayed. (Just click the start on all clocks, then search for a project, then clear your search input.)
I assume this is happening because a setState is run onChange of the filter input, which is re-rendering everything and using the clock getInitialState values.
So, what's the correct way to preserve the 'state' of these clocks and the buttons when the filter re-renders the components? Should I not be storing the clock or the button 'states' as genuine React states? Do I need a function to explicitly save the clock values before the re-render?
I'm not asking for anyone to fix my code. Rather, I'm hoping for a pointer in where my understanding of React is failing.
To satisfy SO's code requirement, below is the component that contains each row in the time tracker. The clocks are started via toggleClock. IncrementClock writes the state that is getting cleared out by the search filter. Please see the complete code in the fiddle link above.
var LogRow = React.createClass({
getInitialState: function() {
return {
status: false,
seconds: 0
};
},
toggleButton: function(status) {
this.setState({
status: !this.state.status
});
this.toggleClock();
},
toggleClock: function() {
var interval = '';
if(this.state.status){
// if clock is running, pause it.
clearInterval(this.interval);
} else {
// otherwise, start it
this.interval = setInterval(this.incrementClock, 1000);
}
},
incrementClock: function() {
this.setState({ seconds: this.state.seconds+1 });
},
render: function() {
var clock = <LogClock seconds={this.state.seconds} />
return (
<div>
<div className="row" key={this.props.id}>
<div className="col-xs-7"><h4>{this.props.project.title}</h4></div>
<div className="col-xs-2 text-right">{clock}</div>
<div className="col-xs-3 text-right"><TriggerButton status={this.state.status} toggleButton={this.toggleButton} /></div>
</div>
<hr />
</div>
);
}
})
When you filter, you're removing LogRow components from the rendered output - when this happens, React unmounts the component and disposes of its state. When you subsequently change the filter and a row is once again displayed, you're getting an entirely new LogRow component, so getInitialState() is called again.
(You also have a leak here because you're not clearing the interval when these components unmount using the componentWillUnmount() lifecycle hook - those intervals are still running away the background)
To solve this, you could move the timer state and the methods which control and increment it up out of the LogRow component, so its job is just to display and control the current state but not to own it.
You're currently using the LogRow component to tie the state and behaviour of a project timer together. You could either move this state and behaviour management up to a parent component which will manage it the same way, or out into another object, e.g.:
function Project(props) {
this.id = props.id
this.title = props.title
this.ticking = false
this.seconds = 0
this._interval = null
}
Project.prototype.notifyChange = function() {
if (this.onChange) {
this.onChange()
}
}
Project.prototype.tick = function() {
this.seconds++
this.notifyChange()
}
Project.prototype.toggleClock = function() {
this.ticking = !this.ticking
if (this.ticking) {
this.startClock()
}
else {
this.stopClock()
}
this.notifyChange()
}
Project.prototype.startClock = function() {
if (this._interval == null) {
this._interval = setInterval(this.tick.bind(this), 1000)
}
}
Project.prototype.stopClock = function() {
if (this._interval != null) {
clearInterval(this._interval)
this._interval = null
}
}
Since the clearIntervals being used are an external source of change, you'd need to subscribe to them somehow, so I've implemented the ability to register a single onChange callback, which the LogRow component is doing when it mounts in the snippet below.
The working code snippet below does the most simple and direct thing possible to achieve this and as a result the solution has some discouraged practices (modifying props) and caveats (you can only have one "listener" on a Project) but it works. (This is generally my experience with React - it works first, then you make it "right" afterwards).
Next steps could be:
PROJECTS is effectively a singleton Store - you could make it an object which allows registration of listeners for changes to project state. You could then add an Action object to encapsulate triggering changes to project state so LogRow never touches its project prop directly, only reads from it and calls sideways to an Action to change it. (This is just indirection, but helps with thinking about data flow). See the Less Simple Communication example in the react-trainig repo for a worked example of this.
You could make LogRow completely dumb by listening for all project changes at a higher level and re-rendering everything on change. Passing individual project props to LowRow would then allow you to implement shouldComponentUpdate() so only rows which need to display a change actually re-render.
<meta charset="UTF-8">
<script src="http://fb.me/react-with-addons-0.12.2.js"></script>
<script src="http://fb.me/JSXTransformer-0.12.2.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css" rel="stylesheet">
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css" rel="stylesheet">
<div class="container">
<div class="row">
<div id="worklog" class="col-md-12">
</div>
</div>
</div>
<script type="text/jsx;harmony=true">void function() { "use strict";
/* Convert seconds input to hh:mm:ss */
Number.prototype.toHHMMSS = function () {
var sec_num = parseInt(this, 10);
var hours = Math.floor(sec_num / 3600);
var minutes = Math.floor((sec_num - (hours * 3600)) / 60);
var seconds = sec_num - (hours * 3600) - (minutes * 60);
if (hours < 10) {hours = "0"+hours;}
if (minutes < 10) {minutes = "0"+minutes;}
if (seconds < 10) {seconds = "0"+seconds;}
var time = hours+':'+minutes+':'+seconds;
return time;
}
function Project(props) {
this.id = props.id
this.title = props.title
this.ticking = false
this.seconds = 0
this._interval = null
}
Project.prototype.notifyChange = function() {
if (typeof this.onChange == 'function') {
this.onChange()
}
}
Project.prototype.tick = function() {
this.seconds++
this.notifyChange()
}
Project.prototype.toggleClock = function() {
this.ticking = !this.ticking
if (this.ticking) {
this.startClock()
}
else {
this.stopClock()
}
this.notifyChange()
}
Project.prototype.startClock = function() {
if (this._interval == null) {
this._interval = setInterval(this.tick.bind(this), 1000)
}
}
Project.prototype.stopClock = function() {
if (this._interval != null) {
clearInterval(this._interval)
this._interval = null
}
}
var PROJECTS = [
new Project({id: "1", title: "Project ABC"}),
new Project({id: "2", title: "Project XYZ"}),
new Project({id: "3", title: "Project ACME"}),
new Project({id: "4", title: "Project BB"}),
new Project({id: "5", title: "Admin"})
];
var Worklog = React.createClass({
getInitialState: function() {
return {
filterText: '',
};
},
componentWillUnmount: function() {
this.props.projects.forEach(function(project) {
project.stopClock()
})
},
handleSearch: function(filterText) {
this.setState({
filterText: filterText,
});
},
render: function() {
var propsSearchBar = {
filterText: this.state.filterText,
onSearch: this.handleSearch
};
var propsLogTable = {
filterText: this.state.filterText,
projects: this.props.projects
}
return (
<div>
<h2>Worklog</h2>
<SearchBar {...propsSearchBar} />
<LogTable {...propsLogTable} />
</div>
);
}
});
var SearchBar = React.createClass({
handleSearch: function() {
this.props.onSearch(
this.refs.filterTextInput.getDOMNode().value
);
},
render: function() {
return (
<div className="form-group">
<input type="text" className="form-control" placeholder="Search for a project..." value={this.props.filterText} onChange={this.handleSearch} ref="filterTextInput" />
</div>
);
}
})
var LogTable = React.createClass({
render: function() {
var rows = [];
this.props.projects.forEach(function(project) {
if (project.title.toLowerCase().indexOf(this.props.filterText.toLowerCase()) === -1) {
return;
}
rows.push(<LogRow key={project.id} project={project} />);
}, this);
return (
<div>{rows}</div>
);
}
})
var LogRow = React.createClass({
componentDidMount: function() {
this.props.project.onChange = this.forceUpdate.bind(this)
},
componentWillUnmount: function() {
this.props.project.onChange = null
},
onToggle: function() {
this.props.project.toggleClock()
},
render: function() {
return <div>
<div className="row" key={this.props.id}>
<div className="col-xs-7">
<h4>{this.props.project.title}</h4>
</div>
<div className="col-xs-2 text-right">
<LogClock seconds={this.props.project.seconds}/>
</div>
<div className="col-xs-3 text-right">
<TriggerButton status={this.props.project.ticking} toggleButton={this.onToggle}/>
</div>
</div>
<hr />
</div>
}
})
var LogClock = React.createClass({
render: function() {
return (
<div>{this.props.seconds.toHHMMSS()}</div>
);
}
});
var TriggerButton = React.createClass({
render: function() {
var button;
button = this.props.status != false
? <button className="btn btn-warning" key={this.props.id} onClick={this.props.toggleButton}><i className="fa fa-pause"></i></button>
: <button className="btn btn-success" key={this.props.id} onClick={this.props.toggleButton}><i className="fa fa-play"></i></button>
return (
<div>
{button}
</div>
);
}
})
React.render(<Worklog projects={PROJECTS} />, document.getElementById("worklog"));
}()</script>