Reactjs component.setProps() alternative, how to send variable to detached element - javascript

I am using things like:
var MUSIC = React.renderComponent( Music({ }), document.getElementById("music-div"))
to later in the script, in an independent element (so not parent of MUSIC) do:
MUSIC.setProps({ url: 'http://...' })
to send a song to de music player, which is detached from the rest, so it does not accidentally gets refreshed by react, because it was programmatically generated (wavesurferjs)
Fine, however, the recent few updates (.11.x) have apparently deprecated that. I do understand where they are coming from, it fits the whole React logic.
However, how will we now ever programmatically modify state/props from outside? Even when I want to talk directly to the parent, which should be allowed.
The changelog tells me in this case the MUSIC variable would have become a descriptor, however, in consoles out the exact same object as far as I can tell. And the documentation says nothing about this descriptor and even less about alternative possibilities.
(http://facebook.github.io/react/blog/2014/07/17/react-v0.11.html#descriptors)
So, if I have two divs
<div id="main-div"> <button></button></div>
<div id="music-div"> </div>
And want to keep them separate, how would I go about giving two parallel parents each-other props?
I don't want to put both in one react div, which would not even solve my problem, because, how would the button in main-div give the props to music-div?
Or would their conceived alternative be to just create a new instance on that id and hope it diffs to 0?
The update states:
"You could store that reference and then call functions on it (eg
component.setProps(...)). This no longer works."
However, that still does work, with (0.11.1) so I don't understand what they are talking about?

You've got several options.
Wrap both main-div and music-div with an "Application" component. Pass a handler down that changes its state so the div's are re-rendered.
Use an event bus to dispatch and listen to events. Basically a component exposes its private setState/setProps() methods to the event bus in a listener. The other component dispatches an event that triggers that listener.

Related

Change detection doesn't happen in synchronous codes

I was working on a component that has a button which toggles a boolean. This boolean is supposed to determine if a child component in the HTML need to re-render or not, since I want the ngOnInit function in the child to be re-run.
The situation is described in the app component here: https://codesandbox.io/s/angular-qxtm8
The app.component is the parent and second.component is the child.
I have tried three different solutions. They are onTestClickOne, onTestClickTwo, and onTestClickThree in app.component.ts. onTestClickOne and onTestClickTwo successfully re-triggers the ngOnInit in the child component. We can see the console log in it is printed on the console whenever I click the corresponding buttons. However, onTestClickThree didn't work.
I'm not 100% sure why onTestClickThree didn't work, and onTestClickTwo did.
My guesses are the following:
onTestClickTwo works because the change detection in Angular is run after the event handler has been executed. So, it will detect the boolean has been set to true. After that, the event loop will get the callback of the setTimeout and put it into the stack. Angular will execute change detection after finishing the callback.
onTestClickThree didn't work because, by the time Angular runs change detection, the boolean is already true. Angular doesn't know that it has been changed.
Let's tackle the main issue there, which is your design : why would you re-render the component to trigger ngOnInit again ?
Sure, in the case of your example, that's no big deal. But what happens for a fully coded component, making http calls, having children and all ? That will cause some severe performance issues.
Instead of re-rendering the component, you should use a function to do that.
If the event (that is initially supposed to re-render the component) comes from the child, then use an #Output. If it comes from the parent, use a #ViewChild reference.
As you can see it works well, without any detection issue.

Angular change detection slow

I have a large array that I am using in a component (component A) with *ngFor with a nested *ngFor.
Component B initialises a jquery plugin which registers a document mousemove event handler, I am using this.zone.runOutsideAngular to init the plugin and I am calling this.ref.detectChanges() in the callback as I need to update the UI on mousemove inside the component B.
Component A is not a child of component B.
As soon as the component A is rendered change detection becomes very slow. the array does not change and I am using the ChangeDetectionStrategy.OnPush strategy for component A but when I fire ref.detectChanges() inside component B, ngDoCheck gets called on component A and I can see a noticeable jank on mousemove.
Is there a way to tell angular to completely ignore the large array of items in component A and allow me to handle when the UI should be updated? I thought that using ChangeDetectionStrategy.OnPush would give me what I need but I have tried removing all #Input()s from component A and anytime I call this.ref.detectChanges() inside component B it is still firing ngDoCheck and it is obvious that this is very slow.
I can scroll through the list of items no issue, but it is when I am triggering the detectChanges inside the mousemove on component B that is causing the issue. I know I could manually update the DOM but I think this would just be a workaround as it would only address the jank on mousemove and not the issue around the change detection being slow.
I have got to the bottom of this issue.
The problem was that inside component A for the nested *ngFor I was using a child component to render each sub item which meant that although I was using the ChangeDetectionStrategy.OnPush strategy, it still required a ref check for each item.
I have now moved the html from the child component into component A directly and this has had a huge impact on performance.
this.ref.detach() to remove the detector from from the tree completely, that should stop the checking. Then you can still call detectChanges to do it manually, and reattach to bring it back online.
Maybe also debouncing the mousemoves (rxjs debounceTime()) might help, unless you really need to track every mousemove?
One more optimization if you already didn't, add trackBy: yourTrackByFn to the ngFor(s).

What constitutes an appropriate use of ref in React

Can someone explain how ref is used in React? I understand it's a shortcut that sort of defeats the purpose of the react DOM, but I don't know how or why exactly. I'm trying to determine whether something I'm trying to accomplish represents one of the rare cases where you should use ref
I want a custom bootstrap alert I can show from any of these pages, without using JQuery (I have one working with JQuery already)
I have a router that switches between pages, each containing a Layout component with a Page component inside (based on this)
Like:
render() {
return (<Layout ref={layout => (this.layout = layout)}>
<WhateverPage
session={this.session}
otherExampleProp={"something"}
showAlert={this.showAlert}/>
</Layout>);
}
showAlert(type, text, hasTimeout, timeoutMs) {
this.layout.alert.showAlert(type, text, hasTimeout, timeoutMs);
}
I can think of three solutions:
Use ref, which as I only partially understand defeats the purpose of react components to some extent, but I'm not sure how exactly...
Use ref, but to a lesser extent, by placing the alert component in each Layout before the Page component (so no need for a ref to ).
Create a component and a function on each page, using the page's state to control the alert, so it would be basically the same as creating a unique alert for each page, which also defeats the purpose of a component...
The example most people give when explaining what to use ref for involves focus() - is this similar? Intuitively it feels like I should use ref, but I also know that theoretically you shouldn't, but I want to understand why, because there are exceptions and for all I know this may count.
Similarly, I want to create a confirm component to replace the native JS confirm() (since it might be deprecated soon), and this approach (using ref) also makes this WAY easier than creating a component for each page, since I can pass any function as a parameter to the confirm component for it to execute on an OK button press (also gives me the option of including icons, titles, custom buttons, etc).
Existing examples and libraries all seem to use method 3 (or they're simpler, and not not really analogous).
Is ref ok to use here? Is it wrong? Why? Am I overthinking this?
Yes, you are "misusing" ref here because you're trying to build around how React is intended to be used.
ref is mainly for accessing the actual rendered DOM element - maybe to focus it, read input, get dimensions, whatever. Generally speaking though you should us ref as a "read only" feature - use it to get info about the rendered DOM but don't use it as part of a process to bypass render() or inject elements into the DOM.
What you should do is create a reusable component for your Alert. Make it flexible enough that it can accept arbitrary settings like color, text, duration, callback functions for accept/cancel/clear, etc. Then you can just render it somewhere, maybe like this:
<MyAlert
title="foo"
text="bar"
duration={5}
confirmCallback={someFunction}
cancelCallback={anotherFunction}
/>
Remember that components are a way to render and interact with state, and that's exactly what you're trying to do with your Alert. There is some kind of notification, it has content and controls for doing some action(s), and all of that should live somewhere in your app state hierarchy. There is absolutely no reason to resort to refin this situation.
To complement the jered answer:
If you plan to use the alert component in all the pages then instead of placing an alert component inside each page you can create a high order component named like PageWithAlert (I would say just Page) that includes the alert component for each page.
You might want to take a look at this https://facebook.github.io/react/docs/higher-order-components.html

Why does React/Redux reset all event handlers, even if `state` is unchanged

To create a onClick event that logs the id of a component, I use this function:
const onClick = (e) => {
console.log(e);
};
const Bar = ({id, text}) => (<div onClick={onClick.bind(null,id)}>{id}: {text}</div>);
This already works correctly, it logs the id of the item I click.
However, according to Perf.printDOM() React apparently touches every node with this handler and creates a new one when the state is updated, even if its unchanged.
Is this intended design or an error in my design? Do I misunderstand what's happening? Would it be premature optimization to find a way to skip this?
Here is a Jsbin for demonstration
You are creating a new event handler every time the component is rendered (which is going to happen every time an event gets handled or state changes or the like). For that reason alone I'd advise against calling bind() inside a functional component, and using either a standard component defined using React.createClass or using an ES6 class inherited from Component instead.
Essentially you should design your components assuming that render() will be called many times, and to avoid allocating or binding as much as possible in that method.
React also has it's own event handling system that mimics the native DOM one, but works a bit differently under the covers. It handles all events at the root component level, and determines which component's handlers to fire based on the source of the event. The official docs are a great place to dig in to how this system works, and of course there always the source code.

VueJS connect unrelated (as in parent/child) components

I am exploring vue.js and have a question regarding how a certain problem can be addressed.
My root component has the following template:
<div class="container">
<div class="stage">
<map :current-time="currentTime" :recording="recording"></map>
<player :track="track" :current-time="currentTime"></player>
</div>
<control-panel :current-time="currentTime"></control-panel>
</div>
Basically, <player> component has <video> element inside that will load the specified track (with native controls hidden). As such, it will in reality drive the state of the application as the video plays through (current time, playback state, etc). However, <control-panel> has a scrub bar and buttons that dictate the video state (play/pause, seek). Obviously, altering this general state in one of the components will affect the other two components (map will also progress according to the current time).
However, I wonder if it would make more sense and whether Vue supports providing references to components so that I could provide <control-panel> with a reference to <player> so that it could take the state changes directly from it.
Or should this be done in a kind of global-state-passed-down-to-children or a event-broadcast way? Before I am corrected, consider an example where there are two <player>s and two <control-panel>s that are not hierarchically related but one panelA works with playerA and panelB with playerB. In this case, I think broadcast option falls off the table, correct?
Any suggestions are welcome especially so as I'm just learning Vue.
Update 1
So after getting a bit more familiar with Vue and hearing back from the community, I think I've come up with a clever solution for syncing <player> and <control-panel> together. My markup changes to the following:
<div class="container">
<div class="stage">
<map :current-time="currentTime" :recording="recording"></map>
<player :track="track" :current-time="currentTime" v-ref:player></player>
</div>
<control-panel :current-time="currentTime" v-ref:player-controls :player="$refs.player"></control-panel>
</div>
Notice the addition of v-ref attributes to <player> and <control-panel> as well as :player="$refs.player" attribute in the latter. This allows me to tie logically together one to another. In my head, it makes sense for control panel to know who or what it is controlling. I'm going to test it out further but, for now, this seems to work.
As for tying together <map>, I will end up using broadcasting or simply two-way currentTime updated by <control-panel>. I'll update this post as I go and will either mark the correct answer or post my own, if different from any of the answers.
Update 2
Read my answer below. I have been able to resolve my issue successfully using the approach below.
I think it is a good idea to leave them as independent components;
you could then switch out the control-panel component whenever you'd like with another that uses the same interface
( just like switching out Stripe with some other payment processor in an e-commerce app )
$broadcasting and $dispatching works well even with multiple players present as long as you pass along an identifier key with each broadcast/dispatch so that each component that receives the message can immediately determine if it was sent for them.
The solution involved taking the approach described in Update 1. Because the <video> element has a well thought through API, translating it into a reactive component was rather easy (although tiresome).
The wrapper for a video element ended up creating several computed attributes in the following manner:
'currentTime': {
cache: false,
get: function () {
return this.$els.player.currentTime;
},
set: function (value) {
this.$els.player.currentTime = value;
}
}
Property cache: false has to be used because the attribute is not tied to a reactive element and needs to be recomputed every time the attribute was queried.
Not making the component fully reactive was intentional. That is, I could have defined needed attributes as props with assumption that they could be provided by and synced with the parent. Those attributes would internally be updated whenever <video> element would fire a state changing event.
I also ended up propagating <video> element's events out to the component's parent using $emit method. This was done because the components simply introduced HLS and acted as a state driver. As such, it was acceptable for it not to be fully reactive and mimic the plain <video> element.
The controls panel successfully referenced the player component asstated in the Update 1. Having received a handle on the player, it was easy to subscribe to its events with $on and then simply hit against its computed properties upon the state changing event.
Finally, map was a little bit easier to implement because it followed the more or less simple flow described in all other Vue examples. In reality, the parent of map kept track of current time. This variable was actually updated by the control panel using :currentTime.sync="currentTime" attribute. However, to the map, it felt the same as if the parent itself was updating the time.

Categories