I've an HTML table generated by React render() which is tied to a websocket for realtime updates.
What I'm looking to do is attach a React event handler "onClick" to the cell which replaces the value in the cell with a custom piece of DOM such as some Bootstraps dropdown HTML so the user can update that cell.
I can use .getDOMNode() in the click event handler and then manipulate the DOM manually and insert the code, however if render() on this component was called due to a new updated state from a websocket event it would be overridden. If this race condition occurs, I need to inform the user instead of the DOM simply being replaced. I've just seen componentWillUpdate() though this still feels like I'm not using React correctly.
Is there a better approach ? It feels dirty to be manipulating the DOM and incorrect to change the state.
Updating internal component state is there for exactly this case. This component should be in charge of determining whether it is active or not. If you update state on the component when the click handler is triggered with something like active: true, then you trigger a re-render. In your render function add the additional DOM elements if active is true. Then you have three different possibilities that should be accounted for:
onBlur
onUpdate which can
be triggered in two ways. either way you'll likely want to set
active: false on state.
websockets - you will need to inform your
user here probably regardless of if the component is active or not
user - which will just update normally
Doing it this way means that React is always in full control of the DOM, which is a really good way to avoid issues with React.
Related
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.
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).
I already saw a similar question from someone else here and the answer is pretty clear. Yet, if you're working with in a redux container, the states turn into props through the mapStateToProps function.
How is it then correct to proceed if I want e.g. my text-div to turn into an input, when I click or double-click on it? I don't have any code yet, I'm wondering about the theory.
The components get store state data passed in as props, but they can also have their own state. So in your case, your component would use it's own state to handle the toggling of div to input. If for some reason you wanted it to be saved in the store instead, you would fire off an action to toggle the view and then use the prop from the store in your render method.
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.
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.