Angular detect when the DOM of a component finished rendering - javascript

I've been search for quite a while, but i wasn't able to find a good solution to this.
I have to trigger a function/event, as soon as the DOM of a component finished rendering.
I need to access the height of the component, so ngAfterViewInit won't work, as that is called before the DOM is rendered.
I tried ngAfterViewChecked, but this gets called multiple times. As the function that gets executed is part of a wrapper-app, i have no access to that either, and i absolutely need to make sure it only gets fired once. I was thinking about using ngAfterViewChecked with some sort of debounce, but this feels (and probably is) wrong.
The alternative would be to trigger a function from the template, when the ngFor loop reaches the last item. But again, that just feels wrong.
Any input is appreciated

I got around this issue by using ngAfterViewChecked. On every check, i get the scrollHeight of the components body. Before it renders, it will be 0, so checking if there's been a change on the scrollHeight attribute, works for my use case.
Thanks for all the comments, that actually made me think of this solution

Related

How should you ensure state update order in react?

I have a couple set states on providers that trigger a bunch of effects throughout my react app. They happen back to back in a hook. The second set state has some effects that it triggers that need make sure that the first hook fully propagated through the app. Currently the first one has not made all the required changes so some functionality triggered by the second causing weird behavior. How do you ensure that the second one only happens after the first is fully propagated?
setSomeStateValue(x);
setValueToTriggersEffectsThatRelyOnUpdatesFromTheOther(y);
Here are some thing I have done that work but have their issues:
1.) settimeout(...,0)
setSomeStateValue(x);
setTimeout(otherSetState,0);
This pushes the second one to a subsequent batch. I like this one because it doesn't involve adding extra code to watch other state variables that the code maybe shouldn't be concerned with, but it does seem a little black magicy and could possibly cause hard to debug issues.
2.) monitor the stuff i need set before calling the second set state
This one seems a little more readable but involves importing and watching things that might not make sense that they belong in the related code. Basically adding a useEffect that watches everything I need set before the second call happens. Also if something changes in regards to what is needed to have the second call ready then this code will have to change as well where as the first solution should not require an update.
Both of these work but have their fallbacks. I would like to refactor the second call to account for these issues but that would be too large of a refactor at this point too make it a feasible option. Is there a native way to ensure this or another strategy here that I am missing? And if not, which one of the above solutions is better?
In such situations where a setState is dependent on a previous setState or a specific state of the component, you can potentially do two things. First,
use the second argument of setState. Here, callback is only invoked once setState is done updating the state. Hence you can get the desired synchronous behaviour.
setState(updater, [callback])
Use componentDidUpdate lifecycle method ( For Hooks, it would be just another useEffect ). If you don't want to have complex setStates in your component, simply have an effect with a dependency on the needed state and do your operations there
React.useEffect(() => {
if( desired_state){
secondSetState()
},
[ desired_state ]
}

setState and setTimeout

Sometimes setState doesnt working all by itself and i am adding small timeouts to do it.
My first question is;
1-) Why setState sometimes doesnt working without a delay?
2-) Is it correct to add setTimeout? - And its small amount of delay like 10 ms, its nothing, but it makes my code to work and state to update.
When i was coding in Angular1, we were using $timeout sometimes for scope to apply this changes. But it was okay to do so, in React i am not sure about this.
Any help would be appreciated, thanks.
It sounds like you're attempting to call setState in a render. This is bad because render occurs immediately following state updates. It causes a render loop. You should not use a setTimeout to do this either. That causes React to stop warning you because it can no longer detect the issue. This will cause large random bugs in your app.
Your symptoms are a sign that you need to push the state up into container components.
The reason why there is a delay in setting the state is that 3 lifecycle method of ReactJS are called while setting the state.
setTimeout is a way to get the current value of the state which has just been set, However another alternative would be that you can create a callback function inside the setState to get the current value.
Use of setTimeout must be always be avoided until and unless there is no other alternative.
example:
this.setState({show:true},()=>{console.log(this.state.show)})

Imitating a click on a dynamically added element with vanilla JavaScript

I have a ReactJS-based website which I wanna navigate programmatically. Basically, the workflow is looped:
Click on Element1.
The web page code is dynamically altered, during which Element2 is added.
Click on Element2.
Etc, until, after clicking on ElementN, the web page returns to the initial state and Element1 is displayed again. Clicking on those elements is what I wanna automate from within the website itself.
I have an access to the JS file that is responsible for the website contents creation and alteration. I can locate the code which describes the elements I'm interested in, and add any extra event listeners if need be.
I'm almost completely new to the client-side development, so my approach to solving this task is purely intuitive. So far my idea was to add an event listener for some sort of "onAdded" event which would fire when the element is added to DOM, and from that listener call the "onclick" listener (or dispatch the "click" event in some other way). However, i can't find any events that would indicate an addition to the DOM tree.
So, strictly speaking, i have two questions:
Is the approach described above viable (and adequate)? If so, then how exactly do i accomplish it?
If i'm doing it all wrong then what would be the right way to accomplish my task?
Edit 1
As per Matthew Herbst's suggestion, I looked into React lifecycle methods like componentDidMount. Turns out, the elements I wanna automate clicking on are not independent React component but some other component's contents added inside the render method with a huge chain of createElement calls.
So now the problem switches from detecting a moment when a particular element is added to the DOM structure to finding a way to interact with it.
The easy (and ugly) way to do it, as I currently see it, is to use the window object from componentDidMount, locate the element I wanna click by its data-reactid attribute (which is a string of dot-separated digits which, from what I can tell, is generated dynamically and reflects the element hierarchy) and then dispatch the required DOM event.
It might work, but if the document structure changes then the values of the data-reactid then it's all broken again. I would prefer to somehow dispatch the React's own onClick event properly, but I don't know how. I tried calling the function that gets passed to the createElement method as a value of the onClick property from componentDidMount, but for some reason it doesn't work.
I have also tried the method of interacting with DOM described in this article, but in my case the ReactDOM object doesn't seem to be defined.
Since I'm not much of a client-side developer, especially not a React guru, I don't really see the whole picture of how the website's client logic is working, and the code I'm working with seems to be minified/obfuscated to make it even harder. So if anyone could provide any specific suggestions without sending me to read all the React documentation, I'd be grateful.
Okay, I'm not sure if a question as profane as mine should be answered at all, but it looks like I got it figured out.
The minified/obfuscated code I had to deal with still had prominent features of a React application, such as objects with series of callbacks like render, componentDidMount or componentDidUpdate. So, just like Matthew Herbst suggested, I looked into the things they do.
The elements clicking on which I needed to automate were created inside the render methods by long chains of createElement calls which looked something like this:
T["default"].createElement("li", null, T["default"].createElement("div", {
className: P["default"].img
}, T["default"].createElement("img", {
src: n(600),
alt: ""
}), T["default"].createElement("b", null , "+3")), T["default"].createElement("h4", null , "«Header text»", T["default"].createElement("b", null , "+3")), T["default"].createElement("p", null , "Description"), T["default"].createElement("div", {
className: P["default"].btn
}, T["default"].createElement(A.Btn, {
mod: "info",
onClick: this._router.bind(this, "/gtr", "gtr")
}, "Play")))
I should have posted that code in the original post, but back then I didn't really understand what's going on here.
It turns out, the object referred to by T["default"] is a React object, meaning that T["default"].createElement calls were actually equivalent to the React.createElement ones.
Then, the article by James K. Nelson helped, which explained that in order to locate a specific child of a component I need to assign a ref attribute to it. I needed to access that "Play" button, so I tried adding a ref property to what looked like its descriptor object, so it would look like
T["default"].createElement(A.Btn, {
mod: "info",
ref: "automatedElement1",
onClick: this._router.bind(this, "/gtr", "gtr")
}, "Play")
, and it worked. Now, inside the componentDidUpdate method I could use the code like
if (this.refs.hasOwnProperty("automatedElement1")) {
var buttonElement = this.refs.automatedElement1;
}
Unfortunately, I didn't quite understand how to get from this object to its DOM reflection, but by simply studying its contents I managed to find a property which corresponds to the value of the data-reactid attribute of an HTML element of that button. So then I found no smarter solution than to acquire the element's DOM node by using the document.querySelector method:
var buttonNode = document.querySelector('button[data-reactid="' + buttonElement._reactInternalInstance._rootNodeID + '"]');
if (buttonNode) {
buttonNode.click();
}
This might be a bad solution, but that's the solution I managed to find in my situation, and it works. Hope it helps anyone who had a rough encounter with a React application and didn't know where to start.

Directive not rendering until any DOM change is made

This is pretty strange. I have an Angular directive that is part of an ng-repeat, and there are no elements in the ng-repeat's source array until a specific action is performed by the user.
Once that action is performed, the array is populated correctly and the element is added to the DOM -- but doesn't actually render. On literally any DOM change -- altering any CSS property of any element on the pages, resizing the page, anything -- the element appears as expected.
Not sure what code would actually be useful to help diagnose this problem, but I'm happy to provide anything relevant.
Other relevant information: Works as expected in Safari, using Ionic framework.
It's not enough that the user perform an action, Angular must find out that the user has done so. You are supposed to inform it by calling $digest() but as that can fail if you are already in a digest call, I just call $timeout().

Angular.js change on one item of ng-repeat causing filters on all other items to run

I'm still running into the same problem, filters and functions inside ng-repeat being called all the damn time.
Example here, http://plnkr.co/edit/G8INkfGZxMgTvPAftJ91?p=preview, anytime you change something on a single row, someFilter filter is called 1000 times.
Apparently it's because any change on a child scope bubbles up to its parent, causing $digest to run, causing all filters to run(https://stackoverflow.com/a/15936362/301596). Is that right? How can I prevent it from happening in my particular case?
How can I make it run only on the item that has changed?
In my actual use case the filter is called even when the change is not even on the items of ng-repeat, it's so pointless and it is actually causing performance problems..
// edit cleared all the unnecessary stuff from the plunker
http://plnkr.co/edit/G8INkfGZxMgTvPAftJ91?p=preview
This is just how Angular's dirty checking works. If you have an array of 500 items and the array changes, the filter must be reapplied to the entire array. And now you're wondering "why twice"?
From another answer:
This is normal, angularjs uses a 'dirty-check' approach, so it need to call all the filters to see if exists any change. After this it detect that have a change on one variable(the one that you typed) and then it execute all filters again to detect if has other changes.
And the answer it references: How does data binding work in AngularJS?
Edit: If you're really noticing sluggishness (which I'm not on an older Core 2 Duo PC), there are probably a number of creative ways you can get around it depending on what your UI is going to be.
You could put the row into edit mode while the user is editing the data to isolate the changes, and sync the model back up when the user gets out of edit mode
You could only update the model onblur instead of onkeypress using a directive, like this: http://jsfiddle.net/langdonx/djtQR/1/

Categories

Resources