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.
Hello i have an array called info[] in a grandchild component and i want my parent component when a button is clicked to access the array. I also want a sibling component to have access to it. How is this possible .. i am a bit confused.
Should I use use-context ?
Thank you!
If I have understand what you are asking it could be something like this.
const GrandChild = ({ setParentInfo }) => {
const info = [1, 2, 3];
const handleClick = () => {
setParentInfo(info);
};
return <button onClick={handleClick}>Set parent info</button>;
};
const Sibling = ({ parentInfo }) => {
return <div>{parentInfo.length}</div>; // Do whatever you need with parentInfo
};
const Parent = () => {
const [parentInfo, setParentInfo] = useState([]);
return (
<div>
<GrandChild setParentInfo={setParentInfo} />
<Sibling parentInfo={parentInfo} />
</div>
);
};
Here you don't need context because you don't have that much layers but if you need to drill down the props than use a context.
If you want to share state among many different components in your application, and you believe that passing State as a prop is "a very long journey" to move around I'm probably you should consider something like use context hook.
Either way what you just described seems like a simple use case witch will not need context.
What you should do is:
On the parent you have [state, setState]
On the current component pass setStat as a prop to child component and then from child component pass setState as a prop to grandchild component.
Then on grandchild component you can do something like:
props.setState(array).
So now on the parent component the variable state will have been updated with the value array from the grandchild component.
If you want to pass state to siblings component, and by sibling I assume you mean sibling of the parent,
Then you should move the state from parent one level up let's say the parent of the parent.. and do what I've described above.
So create useState high in your component tree,
And pass State and setState as well to children as props, setState will be passed as function, so you can call it on the grandchild component or any other component
I have a parent Component with a state variable that gets changed by one of its child components upon interaction. The parent then also contains some more components based on the data in the state variable.
The problem is that the child component rerenders when the state of its parent changes because the reference to the setState function changes. But when I use useCallback (as suggested here), the state of my parent just does not update at all.
This is my current setup:
function ArtistGraphContainer() {
const [artistPopUps, setArtistPopUps] = useState([])
const addArtistPopUp = useCallback(
(artistGeniusId, xPos, yPos) => {
setArtistPopUps([{artistGeniusId, xPos, yPos}].concat(artistPopUps))
},
[],
)
return (
<div className='artist-graph-container'>
<ArtistGraph addArtistPopUp={addArtistPopUp} key={1}></ArtistGraph>
{artistPopUps.map((popUp) => {
<ArtistPopUp
artistGeniusId={popUp.artistGeniusId}
xPos={popUp.xPos}
yPos={popUp.yPos}
></ArtistPopUp>
})}
</div>
)
}
And the Child Component:
function ArtistGraph({addArtistPopUp}) {
// querying data
if(records) {
// wrangling data
const events = {
doubleClick: function(event) {
handleNodeClick(event)
}
}
return (
<div className='artist-graph'>
<Graph
graph={graph}
options={options}
events={events}
key={uniqueId()}
>
</Graph>
</div>
)
}
else{
return(<CircularProgress></CircularProgress>)
}
}
function areEqual(prevProps, nextProps) {
return true
}
export default React.memo(ArtistGraph, areEqual)
In any other case the rerendering of the Child component wouldn't be such a problem but sadly it causes the Graph to redraw.
So how do I manage to update the state of my parent Component without the Graph being redrawn?
Thanks in advance!
A few things, the child may be rerendering, but it's not for your stated reason. setState functions are guaranteed in their identity, they don't change just because of a rerender. That's why it's safe to exclude them from dependency arrays in useEffect, useMemo, and useCallback. If you want further evidence of this, you can check out this sandbox I set up: https://codesandbox.io/s/funny-carson-sip5x
In my example, you'll see that the parent components state is changed when you click the child's button, but that the console log that would fire if the child was rerendering is not logging.
Given the above, I'd back away from the usCallback approach you are using now. I'd say it's anti-pattern. As a word of warning though, your useCallback was missing a required dependency, artistPopUp.
From there it is hard to say what is causing your component to rerender because your examples are missing key information like where the graphs, options, or records values are coming from. One thing that could lead to unexpected rerenders is if you are causing full mounts and dismounts of the parent or child component at some point.
A last note, you definitely do not need to pass that second argument to React.memo.
I have a situation that an item outside the component might influence the item in the backend. ex: change the value of one of the properties that are persisted, let's say the item status moves from Pending to Completed.
I know when it happens but since it is outside of a component I need to tell to the component that it is out of sync and re-fetch the data. But from outside. I know you can pass props calling the render method again. But the problem is I have a reducer and the state will pick up the last state and if I use an prop to trigger an effect I get into a loop.
Here is what I did:
useEffect(() => {
if (props.effect && !state.effect) { //this runs when the prop changes
return dispatch({ type: props.effect, });
}
if (state.effect) { // but then I get here and then back up and so on
return ModelEffect[state.effect](state?.data?.item)}, [state.status, state.effect, props.effect,]);
In short since I can't get rid of the prop the I get the first one then the second and so on in an infinite loop.
I render the root component passing the id:
render(html`<${Panel} id=${id}/>`,
document.getElementById('Panel'));
Then the idea was that I could do this to sync it:
render(html`<${Panel} id=${id} effect="RELOAD"/>`,
document.getElementById('Panel'));
any better ways to solve this?
Thanks a lot
I resolved it by passing the initialized dispatch function to a global.
function Panel (props) {
//...
const [state, dispatch,] = useReducer(RequestReducer, initialData);
//...
useEffect(() => {
//...
window.GlobalDispatch = dispatch;
//...
}, [state.status, state.effect,]);
with that I can do:
window.GlobalDispatch({type:'RELOAD'});
I have a project where I'm displaying cards that contain attributes of a person in a textfield, and the user can edit the textfield to directly change that person's attribute values. However every time they're editing the textfield it causes a rerender of all cards which slows down the app. Here is an example:
export default Parent() {
const [personList, setPersonList] = useState(/* list of person objects*/);
const modifyPerson(index, property, value) {
const newPersonList = _.cloneDeep(personList);
newPersonList[index][property] = value;
setPersonList(newPersonList);
}
const children = personList.map((person, index) => {
<Person
modifyPerson={modifyPerson}
index=index
/*properties on the person */
/>
});
return <div> {children} </div>
}
export default Person(props) {
const fields = /* assume a list of these textfields for each property */
<TextField
value={props.name}
onChange={(e) => modifyPerson(props.index,"name",e.target.value)}
value={props.name} >
return {fields};
}
So essentially when the child's text field is updated, it triggers a state change in the parent that stores the new value, then refreshes what the Child looks like. There's no button that the user clicks to "save" the values-- as soon as they edit the textfield it's a permanent change. And also the parent needs to know the new values of every Person because there's some functions that require knowledge of the current state of the person list. The Person component contains images that slow down the rendering if done inefficiently.
Is there a better way to make this design more performant and reduce rerenders? I attempted to use useCallback to preserve the functions but I don't it works in this specific design because the property and values are different-- do I have to create a new "modifyPerson" for each exact attribute?
Use React.memo()
React.Memo will check the props passed to the component and only if the props changes , it will re-render that particular component.
So, if you have multiple Person component which are getting props which are explicit to those Person, and when you change a particular Person component which leads to Parent state getting updated, only that Person component which you had modified will re-render because its props will change(assuming that you pass the changed value to it).
The other components will have the same props and wont change.
export default React.memo(Person(props) {
const fields = /* assume a list of these textfields for each property */
<TextField
value={props.name}
onChange={(e) => modifyPerson(props.index,"name",e.target.value)}
value={props.name} >
return {fields};
})
As others have already said React.memo() is the way to go here, but this will still re-render because you recreate modifyPerson every time. You can use useCallback to always get the same identity of the function, but with your current implementation you would have to add personList as dependency so that doesn't work either.
There is a trick with setPersonList that it also accepts a function that takes the current state and returns the next state. That way modifyPerson doesn't depend on any outer scope (expect for setPersonList which is guaranteed to always have the same identity by react) and needs to be created only once.
const modifyPerson = useCallback((index, property, value) {
setPersonList(currentPersonList => {
const newPersonList = _.cloneDeep(currentPersonList);
newPersonList[index][property] = value;
return newPersonList;
})
}, []);