React, wait for DOM to update before unmounting? - javascript

I have a component that uses a js library. Since it's vanilla js I've added a bunch of dynamic eventListners that I want to remove when unmounting the component. I've set up a function to run on Blur or when clicking out side of the component so it would run document.getElementById and then clone that element and replace it. Right after that I update the state to let Parent component know to not render the component anymore so it unmounts.
What seems to be happening is the code to get, clone the element and replace it isn't happening right away and so the state to unmount is running and by the time the cloning and replacing happens the component i already unmounted so it cannot find that element in the DOM anymore. How can I can avoid this
const cleanUp = () => {
const element = document.getElementById(id);
const clone = element.cloneNode(true);
element.parentNode.replaceChild(clone, element);
setUnMount(true);
};

Related

instant html class change by DOM manipulation in React

In React app I have a code to toggle html element's (let's call it element A) class when pushing a button. I do it by mutating a state which contains settings for the whole app. Then when the element A is rendered it checks the appropriate setting and apply or not apply the class to it.
This works, but it takes a long time to re-render everything and thus there is a lag between click and changes applied. So I thought to use a little hack here with classic DOM manipulation to first change class (to see changes instantaneously) and then let React re-render everything.
The issue I have is that the changes are not applied by DOM manipulation, but only later after re-render. When I comment the setState line, the changes are visible immediately.
My question is, how to achieve instant class change and what could be wrong here?
const handleCollapseToggle = (i, el) => {
const helpToggler = document.getElementById(el);
helpToggler.classList.toggle('is-hidden');
console.log('test',helpToggler);
const newCollapsed = Array.from(settings.collapsed);
newCollapsed[i] = !newCollapsed[i];
const newSettings = {
...settings,
collapsed:newCollapsed
}
setSettings(newSettings);
//setTimeout(setSettings(newSettings), 10000);
}
I have also tried the setTimeout to apply a new state (please see on the last commented line of code), but that did not work and even did take less then 10 seconds to re-render. Also the console.log('test',helpToggler) logs out the 'test' word first and then the element itself later after re-render (on the same console log line).

How does React update a component and its children after a state change?

I am watching Paul O Shannessy - Building React From Scratch
And I understand the mounting process very well but I have hard day trying to understand how React update a component and its children
The reconciler controls the update process by this method:
function receiveComponent(component, element) {
let prevElement = component._currentElement;
if (prevElement === element) {
return;
}
component.receiveComponent(element);
}
Component.receiveComponent
receiveComponent(nextElement) {
this.updateComponent(this._currentElement, nextElement);
}
and this is the Component.updateComponent method:
updateComponent(prevElement, nextElement) {
if (prevElement !== nextElement) {
// React would call componentWillReceiveProps here
}
// React would call componentWillUpdate here
// Update instance data
this._currentElement = nextElement;
this.props = nextElement.props;
this.state = this._pendingState;
this._pendingState = null;
let prevRenderedElement = this._renderedComponent._currentElement;
let nextRenderedElement = this.render();
if (shouldUpdateComponent(prevRenderedElement, nextRenderedElement)) {
Reconciler.receiveComponent(this._renderedComponent, nextRenderedElement);
}
}
This is the part of the code that updates the component after state change, and i assume that it should update the children too, but i can't understand how this code achieves that, in the mounting process React instantiate components to dive deeper in the tree but this doesn't happen here, we need to find the first HTML element then we can change our strategy and update that HTML element in another place in the code, and I can't find any way to find any HTML elements this way.
Finding the first HTML is the way to stop this endless recursion and logically this is what I expect from the code, to stop recursion the same way in the mounting process, but in mounting, this demanded component instantiation so we can delegate to the reconciler that will discover that we are dealing with a wrapper instance of an HTML element not a wrapper instance of a custom component then React can place that HTML element in the DOM.
I can't understand how the code works in the update process. this code as I see won't dive deeper in the tree and I think won't update the children and can't let React find the first HTML element so React can update the DOM element, isn't it?
This is the code repo on Github
I created a codesandbox to dig in
Here is the codesandbox I created
and here's a short recording of me opening the debugger and seeing the call stack.
How it works
Starting from where you left off, Component.updateComponent:
updateComponent(prevElement, nextElement) {
//...
if (shouldUpdateComponent(prevRenderedElement, nextRenderedElement)) {
Reconciler.receiveComponent(this._renderedComponent, nextRenderedElement);
//...
in the Component.updateComponent method Reconciler.receiveComponent is called which calls component.receiveComponent(element);
Now, this component refers to this._renderedComponent and is not an instance of Component but of DOMComponentWrapper
and here's the receiveComponent method of DOMComponentWrapper:
receiveComponent(nextElement) {
this.updateComponent(this._currentElement, nextElement);
}
updateComponent(prevElement, nextElement) {
// debugger;
this._currentElement = nextElement;
this._updateDOMProperties(prevElement.props, nextElement.props);
this._updateDOMChildren(prevElement.props, nextElement.props);
}
Then _updateDOMChildren ends up calling the children render method.
here's a call stack from the codesandbox I created to dig in.
How do we end up in DOMComponentWrapper
in the Component's mountComponent method we have:
let renderedComponent = instantiateComponent(renderedElement);
this._renderedComponent = renderedComponent;
and in instantiateComponent we have:
let type = element.type;
let wrapperInstance;
if (typeof type === 'string') {
wrapperInstance = HostComponent.construct(element);
} else if (typeof type === 'function') {
wrapperInstance = new element.type(element.props);
wrapperInstance._construct(element);
} else if (typeof element === 'string' || typeof element === 'number') {
wrapperInstance = HostComponent.constructTextComponent(element);
}
return wrapperInstance;
HostComponent is being injected with DOMComponentWrapper in dilithium.js main file:
HostComponent.inject(DOMComponentWrapper);
HostComponent is only a kind of proxy meant to invert control and allow different Hosts in React.
here's the inject method:
function inject(impl) {
implementation = impl;
}
and the construct method:
function construct(element) {
assert(implementation);
return new implementation(element);
}
When we have no DOMComponentWrapper
If we are updating a chain of Non Host Components like:
const Child = <div>Hello</div>
const Parent = () => <Child />
How does Child get rendered from an update to Parent?
the Parent Component has the following:
_renderedComponent which is an instance of Child(which is also a Component)
renderedComponent has an instance of Child because it gets the type of the "root" Element (the one returned by the render method)
so Reconciler.receiveComponent(this._renderedComponent, nextRenderedElement) will be calling component.receiveComponent(element) of the Child which in turn calls this.updateComponent(this._currentElement, nextElement); (of Child) which calls it's render method (let nextRenderedElement = this.render();)
React completely copy the actual DOM and create the virtual DOM in javascript. In our application whenever we update any of the data that ends up being rendered in our components, React does not rerender the entire DOM. It only affects the thing that matters. So react actually copies the virtual DOM again. This time it applies the changes to the data that got updated.
It will make the change in the red component and then it will compare this virtual DOM to the old DOM. It will see the different part. Then it will apply the DOM changes only to that different component.
The updating phase starts if props or the state changes. If the data at the top level changes:
If it is passing that data down to its children, all the children are going to be rerendered. If the state of the component at the mid-level gets changed:
This time only its children will get rerendered. React will rerender any part of the tree below that node. Because the data that generates the children components' view actually sits at the parent component(mid-level one). But anything above it, the parent or the siblings will not rerender. because data does not affect them. this concept is called Unidirectional Data Flow.
You can see in action in chrome browser. chose the rendering and then enable the painting flushing option
If you make any change on the page, you will see that updated components will be flashed.
UPDATING PHASE
componentWillReceiveProps method is invoked first in the component lifecycle's updating phase. It is called when a component receives new properties from its parent component. With this method we compare the current component's properties using the this.props object with the next component's properties
using the nextElement.props object. Based on this comparison, we can choose to update the component's state using the this.setState() function, which will NOT trigger
an additional render in this scenario.
Note that no matter how many times you call this.setState() in the componentWillReceiveProps() method, it won't trigger any additional renders of that component. React does an internal optimization where it batches the state updates together.
shouldComponentUpdated dictates if the components should rerender or not. By default, all class components will rerender whenever the props they receive or their state change. this method can prevent the default behavior by returning False. In this method, existing props and state values get compared with the next props and state values and return boolean to let React know whether the component should update or not. this method is for performance optimization. If it returns False componentWillUpdate(), render() and componentDidUpdate() wont get called.
The componentWillUpdate() method is called immediately before React updates the DOM. It gets two arguments: nextProps and nextState. You can use these arguments to prepare for the DOM update. However, you cannot use this.setState() in the componentWillUpdate() method.
After calling the componentWillUpdate() method, React invokes the render() method that performs the DOM update. Then, the componentDidUpdate() method is called.
The componentDidUpdate() method is called immediately after React updates the DOM. It gets these two arguments: prevProps and prevState. We use this method to interact with the updated DOM or perform any post-render operations. For example, in a counter example, counter number is increased in componentDidUpdate.
After componentDidUpdate() is called, the updating cycle ends. A new cycle is started when a component's state is updated or a parent component passes new properties. Or when you call the forceUpdate() method, it triggers a new updating cycle, but skips the shouldComponentUpdate() method (this method is for optimization) on a component that
triggered the update. However, shouldComponentUpdate() is called on all the child components as per the usual updating phase. Try to avoid using the forceUpdate() method as much as possible; this will promote your application's maintainability
Another answer might be the structure of the Fiber tree. During execution, react renders a ReactComponent into an object made out of ReactNodes and props. These ReactNodes are assembled into a FiberNode tree (which might be the in memory representation of the virutal dom?).
In the FiberNode tree, depending on the traversal algorithm (children first, sibling first, etc), React always has a single "next" node to continue. So, React will dive deeper into the tree, and update FiberNodes, as it goes along.
If we take the same example,
function App() {
return <div>
<Parent>
<Child01/>
<Child01/>
</Parent>
<Child03/>
</div>
}
function Parent({children}) {
const [state, setState] = useState(0);
return <div>
<button onClick={x => x+1)>click</button>
<Child02 />
{children}
</div>
}
Which React will transform into this FiberNode tree:
node01 = { type: App, return: null, child: node02, sibling: null }
node02 = { type: 'div', return: node01, child: node03, sibling: null }
node03 = { type: Parent, return: node02, child: node05(?), sibling: node04 }
node04 = { type: Child03, return: node02, child: null, sibling: null }
node05 = { type: Child01, return: node03, child: null, sibling: node06 }
node06 = { type: Child01, return: node03, child: null, sibling: null }
// Parent will spawn its own FiberTree,
node10 = { type: 'div', return: node02, child: node11, sibling: null }
node11 = { type: 'button', return: node10, child: null, sibling: node12 }
node12 = { type: Child02, return: node10, child: null, sibling: node05 }
I might have missed something (ie. node03's child might be node10), but the idea is this - React always have a single node (the 'next' node) to render when it traverses the fiber tree.
I think React not re-render parent component first instead of that, React re-render child component first.
Example: A (parent) -> B (child) -> C (child of B)
When A update state C (re-render) -> B -> A
Hey Consider using a Tree data structure for your need, ReactJs follows a unidirectional manner of Updating the state i.e. As soon as the there is a Change in the parent state then all the children which are passed on the props that are residing in the Parent Component are updated once and for all!
Consider using something known as Depth First Search as an algo option which will find you the Node that connects to the parent and once you reach that node , you check for the state and if there is a deviation from the state variables that are shared by the parent you can update them!
Note : This may all seem a bit theoretical but if you could do something remotely close to this thing you will have created a way to update components just how react does!
I found out experimentally that React will only re-render elements if it have to, which is always, except for {children} and React.memo().
Using children correctly, together with batched dom updates makes a very efficient and smooth user experience.
consider this case:
function App() {
return <div>
<Parent>
<Child01/>
<Child01/>
</Parent>
<Child03/>
</div>
}
function Parent({children}) {
const [state, setState] = useState(0);
return <div>
<button onClick={x => x+1)>click</button>
<Child02 />
{children}
</div>
}
when clicking on the button, you will get the following:
- button click
- setState(...), add Parent to dirty list
- start re-rendering all dirty nodes
- Parent rerenders
- Child02 rerenders
- DONE
Note that
Parent (app) and sibling (Child03) nodes will not get re-rendered, or you'll end up with a re-render recursion.
Parent is re-rendered because its state has changed, so its output has to be recalculated.
{children} have not been affected by this change, so it stays the same. (unless a context is involved, but that's a different mechanism).
finally, <Child02 /> has been marked dirty, because that part of the virtual dom has been touched. While it's trivial for us to see it was not effected, the only way React could verify it is by comparing props, which is not done by default!
the only way to prevent Child02 from rendering is wrapping it with React.memo, which might be slower than just re-rendring it.

How to access dom element that is not inside JSX in React?

We are using ReactJS framework, so we don't find element written in the entire code. The only way is to find the DOM element and should set the attribute. I am doing this in app.js.
setTimeout(function () {
const formElement = document.getElementsByTagName('form')
formElement.item(0).setAttribute('autocomplete', 'off')
}, 1000)
The above solution is working fine, but if the page loading is slow, then this will not set the attribute. Is there any other way to find the element and set the attribute? I have also tried to set it using the below code after DOMContentLoaded, but it's not working. Nothing inside this event is working.
document.addEventListener('DOMContentLoaded', () => {
const formElement = document.getElementsByTagName('form')
formElement.item(0).setAttribute('autocomplete', 'off')
})
You don't find the element because 'DOMContentLoaded' gets fired when HTML file completes loading, not when React renders the form.
How about using MutationObserver instead? https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
Also doing that in root level app.js sounds wrong. Can you do it in the component that is closest to the container of the form?
In React a good way to access DOM elements is by using useRef hook. However, if that element you want to query is not in the JSX of your current component, the useEffect hook could be used with normal JavaScript DOM methods. Like so:
useEffect(()=>{
const formElement = document.getElementsByTagName('form')
formElement.item(0).setAttribute('autocomplete', 'off')
},[]);

How bad is it to change the DOM in react?

I'm a noob react developer, and I'm currently building a MERN App.
My question to the community is, in my project I had to modify the DOM multiple times as shown below:
document.getElementsByTagName('body')[0].style = 'overflow: hidden';
I know that changing the DOM very often is not recommended in React.Js. So is it ok if I did it only to cut the body's scroll-bar?
In React, changing the DOM directly is usually bad because the state of the page should come directly from the state in React.
But React will render inside the body - the body can't be something returned by the JSX inside a React component, so doing
document.body.style = // ..
really is the only way to change the body's style.
The time you wouldn't want to do such a thing would be if the element being changed was being rendered by React, eg:
// some functional component
const clickHandler = () => {
document.querySelector('.foo').style.backgroundColor = 'green';
};
return (
<div className="foo" onClick={clickHandler}>foo</div>
);
because you could instead toggle some state inside the component which changes the returned JSX to include the different style.
That said, your approach of
document.getElementsByTagName('body')[0].style = 'overflow: hidden';
should be reconsidered, if possible:
Use document.body instead of getElementsByTagName
Do you really have to set the style of the whole <body>? It would be more inline with React to set the style of an element React is rendering, if possible - and then you can use the method mentioned above, of using state and the returned JSX instead of using DOM methods.

setState vs refs in react.js

I created tabs in react and and now on click I have to change the class of the tabs the tabs classes may be as follows:
1:active
2:previousActive
3:alreadySelected
On click of a tab class become active and check whether it is selected before or not using alreadySelected class and active class from the last active tab is remove and if it is not alreadySelected then add alreadySelected.
Code of one tab in react:
var TabBody = React.createClass({
getInitialState: function() {
return {
class: 'tabBody tab activeTab'
}
},
render: function() {
a.tabBody = this;
return (React.createElement('div', {
className: this.state.class,
ref: 'body',
onClick: handleTabClick
},
React.createElement('span', {}, "Body"))
);
}
});
In order to change the class of the tabs I am doing in two ways and want to know which is effective. Code style one:
var bodyClass = (a.tabBody.state.class).split(' ');
var sleeveClass = (a.tabSleeve.state.class).split(' ');
var neckClass = (a.tabNeck.state.class).split(' ');
if (data === 'tabBody') {
bodyClass.push('activeTab');
var str1 = program.arrayToString(bodyClass);
Interfaces.tabBody.setState({
class: str1
});
}
Code Style 2
a.tabBody.refs.body.classList.remove('activeTab');
a.tabBody.refs.body.classList.add('tabPreviewComplete');
a.tabSleeve.refs.body.classList.add('activeTab');
Which style is good for doing this and why?
The point of react is that you do not need to/ should not update DOM directly. The idea behind react is that you render react components (virtual DOM), and that you let react figure out if and how to update DOM.
Changing classes using refs is a very risky strategy: Your component's state is then no longer in sync with actual DOM, which could bring you into debugging nightmares later on. So I would pose that Code Style 2 (even though it works) violates react principles.
One of the few exceptions for using refs, is to add a listener to a DOM component after it is mounted.
The react way is to put the classNames in state.
And do a setState() to update.
And let react do the DOM update,
which is very likely to be way faster, cleaner, and easier to maintain than getting refs, and changing classNames.
ref means you are using the actual DOM and setState means you are saying to react that please update the specific attribute of the component.
every thing is maintain by react.
On the other hand if you use refs it means you are doing every thing your own and react have no concern to your attributes and properties you are updating.

Categories