Better React component hierarchy for rendering progress bar each second? - javascript

I am writing a simple react app that is a music player. It has an album art frame, play/pause button, and a progress bar. The current component structure is which renders and .
The state representation:
{
tracks: //array of audio urls
current: new Audio(tracks[0].url),
artistName: //str
trackName: //str
albumArt: // image url
playing: true,
progress: 0,
duration: 0,
}
However, this presents a problem. We need the progress bar to re-render every second to show the track progress. But right now, inside, , I have an event listener:
currentTrack.addEventListener('timeupdate', () => {
this.setState({
progress: this.state.currentTrack.currentTime
});
});
But this will cause every component to re-render, even though only progress has changed in the state.
I thought about putting currentTrack inside the <Controls> component as its own state, but then the entire controls component will re-render every second. The only other abstraction I can think of is make the progress bar its own component and put currentTrack inside that, but that feels weird. What would be an optimal component structure?

Create a ProgressBar React Component and set the state.progress as property. So every time your player update the state.progress your ProgressBar Component will render.
I control that way my file upload progress and it work well.

Look into shouldComponentUpdate
https://facebook.github.io/react/docs/react-component.html#shouldcomponentupdate
do you have an example you can post where a bit more example is given?

Related

Fix double componentDidMount in React when add jQuery carousel

When I would like to add an old jQuery carousel (jCarousellite) into my React project I have faced with double componentDidMount update in case when I work within React component. Such behaviour of React produces by the dynamical insertion of additional tags (carousel items) into the carousel cause It needs It to some functionality like circular switching. So I have found only one working relatively good solution just to copy the embbed into the main container of the carousel tags to some local variable and use the variable in componentWillUnmount like this
import $ from "jquery"
window.jQuery = $;
require("jcarousellite/jcarousellite");
class Footer extends Component {
main_slider_items; //variable to copy the carousel items into
componentWillUnmount() {
$('.brand_slider ul').html(this.main_slider_items); //insert into the carousel saved during the first mount items
}
componentDidMount() {
this.main_slider_items = $('.brand_slider ul li'); //save items of the carousel here
$('.brand_slider').jCarouselLite({ //assign jQuery plugin to the element
btnNext: '.brand_slider_wrap .next',
btnPrev: '.brand_slider_wrap .prev',
mouseWheel: true,
visible: 4,
scroll: 1,
speed: 150,
circular: true
});
}
render() {
(
{/* some render code here */}
)
}
...
Either If I would like to use indicators functionality of my carousel for instance then I have to do the same trick with them otherwise they also would have been mounted twice and the second time It would be the wrong value. Unfortunalety that solution has at least one disadvantage - items switching within unpredictable movement sometimes but the order of the switching is right. I tried to suppress It somehow by decreasing the speed of the switching but nevertheless I just wonder If there another efficient solution cause sometimes I need to work with very legacy projects and plenty of old code such as various jQuery plugins those wouldn't be rewritten to React style immediately. Thank you.

How to add ngx-loading-bar as preloader into Angular project

I have a project developed in Angular 6+, when user comes to website, first he sees logo and loading bar underneath it.
I have a problem switching to ngx-loading-bar insted of pace.js
Right now I am using Pace (the last example a fixed width line)
https://github.hubspot.com/pace/docs/welcome/
integrated into the index.html of the root project.
I want to put ngx-loading-bar instead of pace to do same job.
ngx-loading-bar is already implemented and working when adding <ngx-loading-bar></ngx-loading-bar> in some of the components but not in the index.html.
I want to put same style same width loading bar as on example link bellow (4th bullet example, similar to pace)
https://aitboudad.github.io/ngx-loading-bar/
so on the load, users sees the loading bar how it loads and on load, it goes on the next component as project example bellow.
Here is the example of project
www.wowlectures.com/pitchdeck
Not sure why you want it to work in index.html, do it in app.component.
You could create a loading service. A simple way is this:
#Injectable({providedIn: 'root'})
export class LoadingService {
private _loading = true;
get isLoading() {
return this._loading;
}
set isLoading(v: boolean) {
this._loading = v;
}
}
Then in app.component.ts:
export class AppComponent {
constructor(public loading: LoadingService) {}
}
And app.component.html:
<any-loading-component *ngIf="loading.isLoading"></any-loading-component>
<router-outlet></router-outlet>
A cleaner way is to use subjects/observables in the service and subscribe to changes where you want to show the loading-bar and dispatch from anywhere you want it to start loading.
<app-root>
<ngx-loading-bar></ngx-loading-bar>
</app-root>
hey you can add this in your index.html

Angular2 - Expression has changed after it was checked - Binding to div width with resize events

I have done some reading and investigation on this error, but not sure what the correct answer is for my situation. I understand that in dev mode, change detection runs twice, but I am reluctant to use enableProdMode() to mask the issue.
Here is a simple example where the number of cells in the table should increase as the width of the div expands. (Note that the width of the div is not a function of just the screen width, so #Media cannot easily be applied)
My HTML looks as follows (widget.template.html):
<div #widgetParentDiv class="Content">
<p>Sample widget</p>
<table><tr>
<td>Value1</td>
<td *ngIf="widgetParentDiv.clientWidth>350">Value2</td>
<td *ngIf="widgetParentDiv.clientWidth>700">Value3</td>
</tr></table>
This on its own does nothing. I'm guessing this is because nothing is causing change detection to occur. However, when I change the first line to the following, and create an empty function to receive the call, it starts working, but occasionally I get the 'Expression has changed after it was checked error'
<div #widgetParentDiv class="Content">
gets replaced with
<div #widgetParentDiv (window:resize)=parentResize(10) class="Content">
My best guess is that with this modification, change detection is triggered and everything starts responding, however, when the width changes rapidly the exception is thrown because the previous iteration of change detection took longer to complete than changing the width of the div.
Is there a better approach to triggering the change detection?
Should I be capturing the resize event through a function to ensure
change detection occurs?
Is using #widthParentDiv to access the
width of the div acceptable?
Is there a better overall solution?
For more details on my project please see this similar question.
Thanks
To solve your issue, you simply need to get and store the size of the div in a component property after each resize event, and use that property in the template. This way, the value will stay constant when the 2nd round of change detection runs in dev mode.
I also recommend using #HostListener rather than adding (window:resize) to your template. We'll use #ViewChild to get a reference to the div. And we'll use lifecycle hook ngAfterViewInit() to set the initial value.
import {Component, ViewChild, HostListener} from '#angular/core';
#Component({
selector: 'my-app',
template: `<div #widgetParentDiv class="Content">
<p>Sample widget</p>
<table><tr>
<td>Value1</td>
<td *ngIf="divWidth > 350">Value2</td>
<td *ngIf="divWidth > 700">Value3</td>
</tr>
</table>`,
})
export class AppComponent {
divWidth = 0;
#ViewChild('widgetParentDiv') parentDiv:ElementRef;
#HostListener('window:resize') onResize() {
// guard against resize before view is rendered
if(this.parentDiv) {
this.divWidth = this.parentDiv.nativeElement.clientWidth;
}
}
ngAfterViewInit() {
this.divWidth = this.parentDiv.nativeElement.clientWidth;
}
}
Too bad that doesn't work. We get
Expression has changed after it was checked. Previous value: 'false'. Current value: 'true'.
The error is complaining about our NgIf expressions -- the first time it runs, divWidth is 0, then ngAfterViewInit() runs and changes the value to something other than 0, then the 2nd round of change detection runs (in dev mode). Thankfully, there is an easy/known solution, and this is a one-time only issue, not a continuing issue like in the OP:
ngAfterViewInit() {
// wait a tick to avoid one-time devMode
// unidirectional-data-flow-violation error
setTimeout(_ => this.divWidth = this.parentDiv.nativeElement.clientWidth);
}
Note that this technique, of waiting one tick is documented here: https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#parent-to-view-child
Often, in ngAfterViewInit() and ngAfterViewChecked() we'll need to employ the setTimeout() trick because these methods are called after the component's view is composed.
Here's a working plunker.
We can make this better. I think we should throttle the resize events such that Angular change detection only runs, say, every 100-250ms, rather then every time a resize event occurs. This should prevent the app from getting sluggish when the user is resizing the window, because right now, every resize event causes change detection to run (twice in dev mode). You can verify this by adding the following method to the previous plunker:
ngDoCheck() {
console.log('change detection');
}
Observables can easily throttle events, so instead of using #HostListener to bind to the resize event, we'll create an observable:
Observable.fromEvent(window, 'resize')
.throttleTime(200)
.subscribe(_ => this.divWidth = this.parentDiv.nativeElement.clientWidth );
This works, but... while experimenting with that, I discovered something very interesting... even though we throttle the resize event, Angular change detection still runs every time there is a resize event. I.e., the throttling does not affect how often change detection runs. (Tobias Bosch confirmed this:
https://github.com/angular/angular/issues/1773#issuecomment-102078250.)
I only want change detection to run if the event passes the throttle time. And I only need change detection to run on this component. The solution is to create the observable outside the Angular zone, then manually call change detection inside the subscription callback:
constructor(private ngzone: NgZone, private cdref: ChangeDetectorRef) {}
ngAfterViewInit() {
// set initial value, but wait a tick to avoid one-time devMode
// unidirectional-data-flow-violation error
setTimeout(_ => this.divWidth = this.parentDiv.nativeElement.clientWidth);
this.ngzone.runOutsideAngular( () =>
Observable.fromEvent(window, 'resize')
.throttleTime(200)
.subscribe(_ => {
this.divWidth = this.parentDiv.nativeElement.clientWidth;
this.cdref.detectChanges();
})
);
}
Here's a working plunker.
In the plunker I added a counter that I increment every change detection cycle using lifecycle hook ngDoCheck(). You can see that this method is not being called – the counter value does not change on resize events.
detectChanges() will run change detection on this component and its children. If you would rather run change detection from the root component (i.e., run a full change detection check) then use ApplicationRef.tick() instead (this is commented out in the plunker). Note that tick() will cause ngDoCheck() to be called.
This is a great question. I spent a lot of time trying out different solutions and I learned a lot. Thank you for posting this question.
Other way that i used to resolve this:
import { Component, ChangeDetectorRef } from '#angular/core';
#Component({
selector: 'your-seelctor',
template: 'your-template',
})
export class YourComponent{
constructor(public cdRef:ChangeDetectorRef) { }
ngAfterViewInit() {
this.cdRef.detectChanges();
}
}
Simply use
setTimeout(() => {
//Your expression to change if state
});
The best solution is to use setTimeout or delay on the services.
https://blog.angular-university.io/angular-debugging/
Mark Rajcok gave a great answer. The simpler version (without throttling) would be:
ngAfterViewInit(): void {
this.windowResizeSubscription = fromEvent(window, 'resize').subscribe(() => this.onResize())
this.onResize() // to initialize before any change
}
onResize() {
this.width = this.elementRef.nativeElement.getBoundingClientRect().width;
this.changeDetector.detectChanges();
}

EmberJS Add Animations to DIV for Fade Out and Fade In on Actions Event

I am new to ember, but have almost finished what I set out to do. I just need to animate things as the final step.
I am using animate.css and animateCSS for my animations.
I have a set of tags. When the user clicks on each button, i call the action onBtnClicked in my controller. When this button is clicked, I basically change the selectedImage property on my controller to a different URL. The selectedImage property automatically updates a tag and the image is changed on the website.
What I would like to do is when the user clicks on the button, to run an animation to transitionOut the selectedImage and transitionIn the new image.
I am able to execute the animations, but the selectedImage updates too quickly and thus the new image is transitionedOut and transitionedIn.
If you have any advice on how to effectively handle the transition out animation it would be greatly aprpeciated.
You can simply first run transitionOut animation, and only when it's complete set the property. Something like this:
actions: {
select: function(item) {
var me = this;
Ember.$("img").fadeOut(function() {
me.set('currentImage', item);
Ember.$("img").fadeIn();
});
}
}
I've made a small demo with jQuery animations (animateCSS seem to use same callbacks) http://emberjs.jsbin.com/hiyatajeve
Out of the box Ember doesn't support animated transitions since when you transition out, it just removes the template partial from the DOM. And Ember.View hooks doesn't support promises.
I personally don't like managing views from controllers, this is another abstraction layer.
So I would recommend you to use Liquid Fire Addon which makes Ember to handle animated transitions in native way.

Update React component when state changes, using a reference to itself

In a React component, I define how the component handles state change in a callback to setState(). This seems wrong / against flux convention. Where is the correct place to define how an element should behave given a new state?
I'm assuming it's in the render() method or the didMount() method.
In this situation, I need to call a JS method on the DOM element:
if (this.state.play) {
document.querySelector(".VideoPlayer-Video").play();
} else {
document.querySelect(".VideoPlayer-Video").pause();
}
I can't do this before the component has rendered. So how & where should this be done?
Example Details:
This is a very simple react component. It has a <video> element, and a button that pauses or plays it.
The state only has one attribute, "play", which is "true" if the video is playing or false if the video is paused.
When the "play" button is clicked, I flip the state, then do a DOM query to get the video element, and call a method on it.
This seems unconventional in React, since I am telling the component how to respond to a state change inside of a click handler. I would expect to define how the component responds to state change elsewhere, but I'm not sure where.
Where is the conventional place to tell the video element to play or not in response to state change? How can I reference the DOM element there?
Code:
var React = require('react');
module.exports = React.createClass({
getInitialState: function(){
return {
play: true
}
},
render: function(){
return <div className="VideoPlayer">
<video className="VideoPlayer-Video" src="/videos/my-video.mov"></video>
<button className="VideoPlayer-PlayButton" onClick={this.handlePlayButtonClick}>
{this.state.play ? "Pause" : "Play"}
</button>
</div>
},
handlePlayButtonClick: function(){
this.setState({
play: !this.state.play
}), function(){
var video = document.querySelector(".VideoPlayer-Video");
if (this.state.play) {
video.play();
} else {
video.pause();
}
}
}
});
Using componentDidUpdate seems appropriate.
1.Click
2. Change state -> trigger - rerender
3. Just after your component is mounted call the right function for video
componentDidUpdate: function() {
if (this.state.play) {
this.refs.MyVideo.play();
} else {
this.refs.MyVideo.pause();
}
}
If you need to control those function for the very first rendering use componentDidMount.
Simple and clean in my opinion.
Edit: I edited my code using ref, I think this is indeed the right way to go
https://facebook.github.io/react/docs/more-about-refs.html
Just place a ref in your video component:
<video ref="MyVideo"> </video>
Updated after mark's comment
Flux is good consideration for long term components, yes it's very big and requires some breaking changes, as François Richard noticed. But still worth it.
If you have a big component, then here's some tips:
You change state of that video component to playing, ONLY when video component receives $event, otherwise it doesn't make sense, as flux heavily uses nodejs eventEmitter, note video components must be explicitly separated, so when you expand your functionality it won't hurt your component.
Simple demo repo.
Methods performed on the DOM should be called within componentDidUpdate, as it is called once react has finished its updates to the DOM.

Categories

Resources