ref issues due to React Portals and componentDidMount - javascript

Background:
I'm trying to figure out the best way to implement a Portal component that wraps React's native portal utility. The component would simply handle creating the portal's root element, safely inserting it into the DOM, rendering any of the component's children into it, and then safely removing it again from the DOM as the component is unmounting.
The Problem:
React strongly advises against side effects (like manipulating the DOM) outside of React's safe life cycle methods (componentDidMount, componentDidUpdate, etc...) since that has the potential to cause problems (memory leaks, stale nodes, etc...). In React's examples of how to use Portals, they mount the portal's root element into the DOM tree on componentDidMount, but that seems to be causing other problems.
Issue number 1:
If the Portal component 'portals' it's children into the created root element during it's render method, but waits until it's componentDidMount method fires before appending that root element into the DOM tree, then any of the portal's children which need access to the DOM during their own componentDidMount life cycle methods will have issues, since at that point in time they will be mounted to a detached node.
This issue was later addressed in React's docs which recommend setting a 'mounted' property to true on the Portal component's state once the Portal component had finished mounting and successfully appended the portals root element to the DOM tree. Then in the render, you could hold off on rendering any of the Portal's children until that mounted property was set to true, as this would guarantee that all of those children would be rendered into the actual DOM tree before their own respective componentDidMount life cycle methods would fire off. Great. But this leads us to...
Issue number 2:
If your Portal component holds off on rendering any of it's children until after it itself has mounted, then any of the componentDidMount life cycle methods of it's ancestors will also fire off prior to any of those children being mounted. So any of the Portal component's ancestors that need access to refs on any of those children during their own componentDidMount life cycle methods will have issues. I haven't figured out a good way to get around this one yet.
Question:
Is there a clean way to safely implement a portal component so that it's children will have access to the DOM during their componentDidMount life cycle methods, while also allowing the portal component's ancestors to have access to refs on those children during their own respective componentDidMount life cycle methods?
Reference Code:
import { Component } from 'react';
import PropTypes from 'prop-types';
import ReactDOM from 'react-dom';
export default class Portal extends Component {
static propTypes = {
/** This component uses Portals to dynamically render it's contents into
* whatever DOM Node contains the **id** supplied by this prop
* ('portal-root' by default). If a DOM Node cannot be found with the
* specified **id** then this component will create one and append it
* to the 'Document.Body'. */
rootId: PropTypes.string
};
static defaultProps = {
rootId: 'portal-root'
};
constructor(props) {
super(props);
this.state = { mounted: false };
this.portal = document.createElement('div');
}
componentDidMount() {
this.setRoot();
this.setState({ mounted: true });
}
componentDidUpdate( prevProps, prevState ) {
if( this.props.rootId !== prevProps.rootId ) this.setRoot();
}
componentWillUnmount() {
if( this.root ) {
this.root.removeChild(this.portal);
if( !this.root.hasChildNodes() ) this.root.parentNode.removeChild(this.root);
}
}
render() {
this.portal.className = this.props.className ? `${this.props.className} Portal` : 'Portal';
return this.state.mounted && ReactDOM.createPortal(
this.props.children,
this.portal,
);
}
setRoot = () => {
this.prevRoot = this.root;
this.root = document.getElementById(this.props.rootId);
if(!this.root) {
this.root = document.createElement('main');
this.root.id = this.props.rootId;
document.body.appendChild(this.root);
}
this.root.appendChild(this.portal);
if( this.prevRoot && !this.prevRoot.hasChildNodes() ) {
this.prevRoot.parentNode.removeChild(this.prevRoot);
}
}
}

The constructor is a valid lifecycle method in which you can perform side effects. There's no reason you can't create/attach the root element in the constructor:
class Portal extends Component {
constructor(props) {
super();
const root = document.findElementById(props.rootId);
this.portal = document.createElement('div');
root.appendChild(portal);
}
componentWillUnmount() {
this.portal.parent.removeChild(this.portal);
}
render() {
ReactDOM.createPortal(this.props.children, this.portal);
}
// TODO: add your logic to support changing rootId if you *really* need it
}

Related

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.

React findDOMNode alternative for text nodes

I'm trying to create a text editor such as Draft.js, which entails using contentEditable. Anyways, I'm using MutationObserver to detect changes and I want to be able to reflect changes to the DOM in the state.
When a component renders to a string, findDOMNode returns a text DOM node containing that value.
I want to compare the mutation target to the rendered dom and reflect those changes in the app state. It would most likely work if I used findDOMNode, however it's deprecated. Is there an alternative? Or, is there a way to use refs to make this happen?
As an example, I have a PElement class:
class PElement extends React.Component {
constructor (props) {
super(props);
this.state = {
children = []
}
/*Parse content props, etc, for children*/
this.ref = React.createRef();
this.compare = this.compare.bind(this);
}
compare (node) {
//This will allow me to check if the target of the mutation was this component.
return this.ref.current === node;
}
render () {
return (<p ref={this.ref}>{this.state.children}</p>);
}
}
As for the Text class:
class Text extends React.Component {
constructor (props) {
super(props);
//No state. This will be lowest level and controlled.
this.ref = React.createRef();
this.compare = this.compare.bind(this);
}
compare (node) {
return this.ref.current === node;
}
render () {
//I don't know how to create a ref to the rendered text node.
return this.props.content;
}
}
}
I tried to create a Text node and use React.cloneElement, though it didn't work.
In the end, I settled on inferring the location of the Text component by mutating the state with each mutation, ie childList by adding or removing components and checking the child nodes of the parent. This way I can know which Component renders which node.
Though, on second thought, I might just prevent input and modify the state in the way which it should be, based on the captured input. It's more in line with the philosophy of React.

What triggered first in react native?

I have a react native component which contains both WillFocus and componentDidMount functions.
My Questions is, if I navigate to this component which function is getting triggered first? 'WillFocus' or 'componentDidMount'
Sample code is showing below
class Notifications extends Component {
static navigationOptions = {
header: null
}
constructor(props) {
super(props);
const{navigation}=this.props
this.state = {
highlightHome : true,
highlightNotifications: true,
}
}
willFocus = [this.props.navigation.addListener(
'willFocus',
payload => {
console.log('willFocus')
}
)]
componentDidMount() {
console.log('componentDidMount')
}
}
React Navigation emits events to screen components that subscribe to them.
componentDidMount:
This method is called once all our children Elements and our Component instances are mounted onto the Native UI. When this method is called we now have access to the Native UI (DOM, UIView, etc.), access to our children refs and the ability to potentially trigger a new render pass.
willFocus:
the screen will focus.
By definition, willFocus will be called after ComponentDidMount because it mounted all the UI components.
componentDidMount call first, but in the next time (back or something...) just willFocus call

render react component when prop changes

how to force render our component when props changes?
how to force render parent component when child component's props changes?
i searched a lot but all solutions are deprecated in new React version.
in my any pages (Route exact path="/post/:id" component={post}) for example (siteUrl/post/1) i am getting (:id) from props (this.props.match.params) and that works.
but when i am in single page component (Post) and this Route (siteUrl/post/1) when i am passing new Props to this component (:id).
props will changes but single component and parent component Will not re render...
You may be using componentDidMount method.
componentDidMount() is invoked immediately after a component is
mounted (inserted into the tree). Initialization that requires DOM
nodes should go here. If you need to load data from a remote endpoint,
this is a good place to instantiate the network request.
but you need to use componentDidUpdate.
componentDidUpdate() is invoked immediately after updating occurs.
This method is not called for the initial render.
You can also use state and other React features without writing a class.
read more: https://reactjs.org/docs/hooks-effect.html
To make both parent and child re-render you need to path prop from parent to it's child.
// parent using
<Parent someProp={{someVal}} />
// parent render:
render() {
const { someProp } = this.props
<Child someProp={{someProp}} />
}
this will surely re-render both components, unless you stated another logic in componentShouldUpdate
in your case Router looks like a parent for Parent so you should only path :id as a prop.
Make sure Router is at the top level, right under the App
Important is ,that you initialise the someVal of the child first in the constructor
public static getDerivedStateFromProps(
nextProps,
nextState
) {
let { someVal } = nextProps;
if (nextState.someVal !== someVal) {
return {
initialVal,
someVal: someVal
};
}
return null;
}
After it will rerender on prop changes because the state changes

React.js - use DOM elements from different levels of component hierarchy

We need to perform some actions related to DOM elements in different parts of the component hierarchy, on window.onresize and window.onscroll:
1) Moving elements when resizing to mobile resolution
2) Changing classes and styles when scrolling.
The problem is that the elements are in different levels of the React component hierarchy, i.e. nested in different components. Thus if I understand correctly, I can't use refs to access the DOM nodes.
I have:
{someCondition ? <FirstComponent/>: <SecondComponent/>}
Whereas someCondition can change either due to user UI actions, or it could be true from the beginning of the page load.
I've tried using componentDidMount and componentDidUpdate on my FirstComponent, but found that componentDidMount only fires if someCondition is true from the beginning, and componentDidUpdate indeed fires when someCondition changes, but then the required DOM elements (contained in the component) are not ready at that point, and document.getElementById fails.
Adding window.requestAnimationFrame did not help.
I imagine there should be a solid way to do this in React?
Maybe React.findDomNode?
Thank you.
What you are describing is the antithesis of React. It is an approach I would expect when using an imperative jQuery/Dojo based framework.
With React, you are developing functional components that know how to convert state into rendered HTML and you need to approach the problem differently.
For your problem, your window.onresize and window.onscroll callbacks should not try to manipulate the React DOM elements. Instead it should just update state and let React re-render. Your React components should receive this state and render accordingly.
Here's a simple example where the main app listens to these events and updates its state, which triggers a re-render. The app passes the state as props to the children. The children use the props to conditionally change their css class names. They could easily render different html or inline styles as well.
const Component2 = ({someCondition2}) => {
const className = someCondition2 ? "foo" : "bar";
return <div className={className}>Hello</div>;
};
const Component1 = ({someCondition1, someCondition2}) => {
const className = someCondition1 ? "A" : "B";
return (
<div className={className}>
<Component2 someCondition2={someCondition2} />
</div>
);
};
class App extends React.Component {
state = {
someCondition: false,
someCondition2: true,
};
componentDidMount() {
window.onresize = ev => {
const someCondition = ev.xxx;
// re-render with the new state
this.setState({ someCondition });
};
window.onscroll = ev => {
const someCondition2 = ev.xxx;
this.setState({ someCondition2 });
};
}
render() {
const {someCondition, someCondition2} = this.state;
return (
<Component1
someCondition1={someCondition1}
someCondition2={someCondition2} />
);
}
}
React.render(<App />, document.getElementById("container"));

Categories