Add DOM nodes to react hierarchy - javascript

How can I insert a DOM node using vanilla javascript into a hierarchy of DOM nodes created by react such that React will not remove it when the state changes.
Reason:
I am developing a browser extension, and would like to tightly integrate with an existing site. The existing site uses React under the hood, and I would like to insert a node into the DOM.
Issue:
Inserting the node works, but as soon as some state in the react app changes, it removes the custom node from the DOM.
How can I insert and element which will not be removed?

I don't think you can (but you might be able to work around it).
React's job is, in large part, to reconcile the state of the DOM with the state of the React element tree the code using React gives it. After the code has "rendered" React elements, React "commits" that structure to the DOM, doing a diff between the structure the React element tree describes and the structure that's there: removing things that shouldn't be there, adding things that are missing, and updating things that should be there but need their state updated. When it does that, it will see the element you added, see that it's not supposed to be there, and remove it.
The first workaround that comes to mind for your use case is to add the element and use a MutationObserver to watch for it being removed, adding it back if (when) it is.

Related

Testing DOM updates in React

In a React component I am using an external library that creates complex components that I modify slightly on render. Thus, in my own component, I use things like element queries and mutation observers to get rendered elements in the DOM and dynamically add my own modifications, depending on the state of the DOM itself (not the external component, since I cannot access its state).
This works great, but I have not been able to figure out how to test this functionality [in Jest]. In particular, I add mutation listeners that add my own small React components based on the HTML that the external component renders, adding a button when the mouse hovers over a list of dynamically-created elements. When I attempt to test this in Jest, none of this logic is performed, and the functionality I have added cannot be tested, as the changes to the DOM do not appear, even with full rendering. In particular, during testing I have found that the queries I am using in the component do not have any values, as they need the external component to fully render the HTML for my own component to observe the changes.
I need a way to test my component in a way such that the external component is rendered, but such that my own component can perform updates afterwards, when its own state changes.
How can I test the functionality of the updates that I perform that are based on element queries if there are not any results to these queries during testing?

Angular Elements WebComponents external DOM Changes

TL;DR: Deleting the DOM Element of a custom Element created with Angular Elements will lead to sub-routers not loading components
First, the code is available at Github.
Sadly, I did not get a stackblitz version running, but locally, after a clone, npm install and ng serve it should run fine.
About the general structure:
The Project shows an example of a WebComponent exposed by an Angular App via Elements.
The WebComponent is used in the index.html, which is referred as container Application.
The use case here is a little bit constructed, but the navigation that occurs, when clicking
on the two top bottoms is comparable to the real world use case.
Normally this WebComponent would have been used in an AngularJS legacy Application. Since there are many overlappings between the two root views (overview1 overview2) it is used in two AngularJS components, in between ui-router handled navigation. This means, that exactly this constructed actions happen. The DOM Elements will be deleted and re-added.
So basically the problem is:
If I am deleting DOM Elements of a WebComponent (so deleting the CustomElement itself) from external, and then re-adding it with another route, The Child-route components of this will not get loaded. If there is a short delay (50ms), everything works fine.
To reproduce the problem:
Load page and click an the "switch with delay" button.
Reveal Detail Component
Switch to the other Overview by clicking "switch with delay" again.
Reveal Detail Component
=> Everything should work fine
Repeat the same steps, but this time click only on "switch directly".
Prior Investigations
What I already debugged is the Router. So I went through the log messages with "enable tracing"
and they seemed to be no differences. Afterwards I compared the Components' Lifecycle and
the thing I noticed is, that in the working example the old Detail**1**Component Object will get destroyed a new Detail**1**Component one will be created and directly destroyed afterwards and then everything regarding the Detail**2**Component will get constructed.
On the not working example it is like this:
A new Detail**1**Component will get constructed again and destroyed afterwards. Then the old Detail**1**Component will get destroyed. Then nothing regard the Detail**2**Component will get constructed.
So routing seems to work fine, but components won't get loaded in this case, maybe due to a strange lifecycle, because of the Detachment of the View to the ViewModel happening due to hard deleting those WebComponent in DOM.
Maybe I am just dumb and did something fundamentally wrong, but I could not find anything in the Web regarding this problem and every solution I tried by myself just resulted in a workaround like establishing a delay.
You can find a workaround available on Github.
I forced the recreation of the DOM object by attaching a boolean
to the navigation button, which will determine if the DetailComponent
should actually get shown.
<router-outlet *ngIf="shown">
Then the Outlet will get reevaluated and the DOM will get refilled on clicking the button.
This will effectively mitgating the issues described.
In my opinion its not the cleanest solution, but cleaner than a timeout, that
even needs to get applied from the outside.
I hope this was helpful and maybe someone might point me to the actual fix and
not only workaround.
Since this was my first question do not hesitate to give me feedback on formal
appearance.

Make Vue interpret components that have been inserted programmatically

I'd like to modify a div inside my Vue component's template using the Rangy library. I have code like this in one of my methods:
let tooltip = document.createElement('button');
tooltip.setAttribute('type', 'button');
tooltip.setAttribute('v-tooltip.top-center', '"msg"');
tooltipSpan.innerHTML = 'x';
// Insert span with tooltip after of mistake
range.collapse(false);
range.insertNode(tooltipSpan);
As you can see in the third line, I also want to use a v-tooltip component. If I could hardcode this inside the template, it'd simply be <button v-tooltip.top-center="msg">x</button>. But in my app, this component could be programmatically placed anywhere inside a div, so I need to insert it with JavaScript. There might also be more than one instance of that component.
Unfortunately, my approach does not work at all. It doesn't matter whether I use the approach mentioned above using the v-tooltip directive or if I simply insert a component (e.g., let tooltip = document.createElement('my-custom-tooltip');): The new DOM element is correctly inserted, but Vue does not recognize/interpret it as a component. In the above example, all I get is a simple, unstyled button element.
I tried this.$forceUpdate(), this.$nextTick() and a bunch of older methods (that have probably vanished from Vue 2), but nothing worked.
Is there a way to make Vue re-render after manual changes to he DOM, correctly interpreting components that have been inserted?
Vue "owns" the DOM tree belonging to any component; if you mess around in the DOM like you are, any Vue-specific things that you create or modify (like directives) won't magically work.
The DOM that is generated by Vue is a function of the component's data; any time the data changes Vue will re-render the DOM to reflect that change. Vue doesn't know how to deal with manual changes you make to the DOM.
I don't know specifically what you are trying to do and why Rangy is needed, but the correct way to do something like this would be to modify your data not the DOM, and then your template (or render function) takes care of rendering the new data.
Is there a way to make Vue re-render after manual changes to he DOM, correctly interpreting components that have been inserted?
No.

how reactjs updates all the changes in dom in single event loop to paint dom only once

I was reading article about dom update in reactjs.Reactjs updates all the changes in single event loop.I understand event loop in javascript and how it works in core javascript.Can any one tell how reactjs uses event loop for dom manipulation
React has a Virtual DOM, doesn't manipulate the DOM directly.
The Virtual DOM is a copy of the HTML DOM and is local to React. We can call it an abstraction of the HTML DOM. So, whatever changes need to be made to the DOM, React does that to this Virtual DOM and syncs the Real DOM accordingly.
React will update the entire Virtual DOM.
When we call the render() method it’ll compare the Virtual DOM before updating, with the one after updating, to identify what objects have been changed. It uses the Diffing Algorithm.
Then compare the Virtual DOM with the real one, and only the changed objects will get updated on the real DOM.

Vue deleting DOM node in created live-cycle hook to initialize a component

I'm currently trying to understand why this example is not working as expected. So what I'm trying to achieve is to initialize my ContentView with the server side rendered HTML present in the DOM. Therefore I intend to check if init__main-content exists to then initialize the component with the innerHTML. Afterwards I simply tried to delete the initial node but this is not working and I end up with the content shown twice.
There must be some vue "magic" I'm not aware of, can please someone explain this behavior?
I guess there is problem in your code as you are using created hook insted you can use mounted hook.
so after mounting vue does finish all its magic to dom and now your changes can be persisted in to the dom.
if you use created then vue may be using that inner html as template and rewrite it it to dom thinking that previous html is not matching with current html when instance is created. as you already removing that element. so it will try to make it correct and add previous html again to maintain virtual-dom and real-dom changes.
if you use mounted hook your application is working fine try it.

Categories

Resources