Why cant virtual dom prevent my child component rerendering? - javascript

I have a parent and child components, In my example, when increase button was clicked, child is gonna be rerendered.
import React, {useEffect,useState} from 'react';
export const App = (props) => {
const [count, setCount] = useState(0);
const handleCount = ()=>{
setCount(count+1);
}
useEffect(()=>{console.log("Parent was rerendered")})
return (
<div className='App'>
<h1>This is parent</h1>
<button onClick={handleCount}>increase</button>
<Child/>
</div>
);
}
const Child = props => {
useEffect(()=>{console.log('Child was rerendered')})
return (<>
<h1>This is child</h1>
</>)
}
I think it is pretty simply. And I also understand that, to prevent child rendering, I could us memo.
However, at some point, I studied the the concept of virtual dom. In it, it explained that the greatest of React is that it can prevent unchanged components to get rerendered, in order to prevent unnecessary computation.
If virual dom exist, isnt it that my child didnt change at all? Why is that my child get rerender eventually?
Edit:
I have to emphasize that my question is not about how to prevent it from re rendering. I knew the existence of memo. My question is virtual dom. React says virtual dom can compare the previous and current UI, then it will automatically prevent unchanged component from re rendering. If that's the case, why my example still got rendered? When virtual dom comes it, it doesnt make sense at all. Please dont neglect my concerns on virtual dom when providing answer

Changing parent state triggers re-rendering the component along with its children. Your child component will be re-rendered when you press 'increase' button without regard to its internal changes in view of the code you shared.
To prevent children from re-rendering, you may use React.memo like below
const Button = React.memo((props: PropsWithChildren<any>) => {
return (
<Button type="button" color={props.color} size={props.size} onClick={props.onClick}>
{props.label}
</Button>
);
});
If you do not use that kind of approach, children will be re-rendered by default.
If you wonder opposite; if state changes in child component, that will not cause parent component re-rendering. But there are ways to make that happen.
NOTE: DOM will not update unless something changed
Further reading
React Memo
UPDATE
I guess there's a misconception. When you press the 'increase` button, there will be re-rendering
The scenario:
When the state of a component changes, React updates the virtual DOM. React then compares the current version of the virtual DOM with the previous virtual DOM.
After comparing both and finding out which objects have changed, React updates them.
React updates DOM only if the render process produces changes. I hope it is now more clear to you.
To better understand, here's an article
React Virtual Dom
EXTRA UPDATE
Let's assume that two red nodes shown in the picture below are your components in the DOM (parent and child respectively)
When you press the 'increase' button in the parent component, it changes the inner state which leads to re-rendering of parent component along with its child component. So two red circles are re-rendered.
React, compares the old Virtual DOM and brand new Virtual DOM. Even if your parent and child component re-rendered, that will not lead to an update on the Real DOM because there's neither a visible change nor a structural change. You can think of it such as showing the current number on your parent component as a text. To visualize it, please take a look at the code below:
export const App = (props) => {
const [count, setCount] = useState(0);
const handleCount = () => setCount(count+1);
return (
<div className='App'>
<h3>Current count: {count}</h3>
<button onClick={handleCount}>increase</button>
<Child/>
</div>
);
}
Now, we have changed your parent component with an additional h3 tag in which we show the current count. In this case, if we press the 'increase' button that will lead to re-render again and this time the Real DOM will be updated because React will find out that there's a difference between old and new versions of the Virtual DOM.
So rendering and updating the real DOM are different processes.
Back to React.memo
If you use React.memo in your child component, React will render it, memoize the result instead of re-rendering and React will not perform a Virtual DOM difference check.

Related

How do you update state in parent component from a child component when an event takes place in the parent component

I have to update state of the parent class component from child functional component only when a click event takes place in the parent component. Right now I've access of the data from child to parent component but when I update the state I get error, maximum depth reached. So I thought it'd be better to update the parent's state from child only when the certain event takes place in the parent component but I'm unable to find any approach. Could you please show some light?
From your description, the best thing to do is to lift state up from the child to the parent so that the information you currently only have in the child is available in the parent (which can provide it to the child as a prop). Then, the child isn't involved in this at all, the event in the parent updates the parent's state, which is standard.
But if you absolutely can't do that:
It's taking you off the beaten path, but you could:
Have the parent pass the child two things:
a means of subscribing to an event from the parent
a function to call to update the parent's state
Have the child subscribe to the event
Have the parent raise the event the child subscribes to when the click occurs
Have the child respond to that event by calling the function from (1)(2) above
While that would work, it's a bit convoluted. So again, if you can avoid it, move the state needed for the update from the child to the parent and keep the entire update in the parent.
Finally I figured out a way to achieve it.
Step 1: Set a boolean for your event taking place in your parent state using useState hook.
// Inside Parent component
const [eventInParent, setEventInParent] = React.useState(false);
Step 2: pass the eventInParent as prop to your child component
// Inside Child component
Step 3: Receive the event status (eventInParent) in your child component in your props and use it as a dependency in useEffect hook
React.useEffect(() => {
if(eventInParent) {
}
}, [eventInParent]);
As you can see every time event will hit in parent component the eventInParent value will change which will trigger the useEffect hook in the child component.

React.memo not working on a functional component

I tried using React.memo() to prevent re-rendering on one of my components but it keeps on re-rendering whenever there are changes on its Parent Component.
Here's my child component with React.memo()
const Transactions = React.memo(() => {
console.log('rerender')
return (
<></>
)
})
Here's where it's being called. Whenever I click the Add button it calls the state function setModal(true), Transactions component is being re-rendered
const ScholarHistory = ({setModal}) => {
return (
<div className="pml-scholar pml-scholar--column">
<div className="pml-scholar-container">
<button onClick={() => setModal(true)} className="pml-scholar-add-button"> Add </button>
</div>
<Transactions />
</div>
)
}
I am new to React and been exploring/creating wider projects. Any ideas why it keeps on re rendering? Thanks!
I tried to remove the setModal prop on the ScholarHistory Component and was able to prevent the re-rendering.
I am not sure what causes it as it has no relation at all to the child component other than being a prop to the parent component - ScholarHistory.
Coming late to the thread by putting it here for anyone still having the same issue.
I think the reason is that setModal prop somehow removes the entire parent component and therefore also removes the cached Transactions component, hence the rerender.
If you just change the parent component's prop (i.e. changing it's state) without removing it from the virtual DOM tree then the child component won't be rerendered.

Is it an anti-pattern to use the props of your child components to render another component?

I have a component in React that can take an indefinite number of children, and it uses those children to generate another component, with the original children composed within it. The point is to wrap the original child component with a new component that decorates the functionality of it. The code looks like this:
const Form = ({ children }) => (
<FormGroupComponent>
{children.map(child => (
<CheckboxAndChild
checked={child.props.checked}
setCheckbox={child.props.setCheckbox}
>
{child}
</CheckboxAndPicker>
))}
</FormGroupComponent>
);
const CheckboxAndPicker = ({ checked, setCheckbox, children }) => (
<div>
<div>
<Checkbox
checked={checked}
onChange={() => setCheckbox(!checked)}
/>
</div>
<div>{checked && children}</div>
<div/>
</div>
);
These two functional components can be used like this:
...
<Form>
<TimePicker
{...timePickerProps}
checked={showTimeOne}
setChecked={setTimeOne}
/>
<TimePicker
{...timePickerProps}
checked={showTimeTwo}
setChecked={setTimeTwo}
/>
</Form>
...
I find two things strange about this code:
The "TimePicker" component doesn't normally have a "checked" and "setChecked" prop within it, and doesn't do anything with those props itself. Those props are only used to render the "CheckboxAndPicker" component within the "Form" component.
The "Form" component accesses its children component's props in order to generate a new component using those props.
Is this an anti-pattern, or is this sort of functionality acceptable when decorating existing components with new props, and using those new props to generate new components?
I decided to remove the Form component, and directly use the FormGroupComponent component. By doing this I removed the need to use the props of its children props, making the code more readable (though a little more verbose). The following is the change I made:
<FormGroupComponent>
<CheckboxAndPicker
checked={showTimeOne}
setCheckbox={setTimeOne}
>
<TimePicker
{...timePickerStyle}
/>
</CheckboxAndPicker>
<CheckboxAndPicker
checked={showTimeTwo}
setChecked={setTimeTwo}
>
<TimePicker
{...timePickerStyle}
/>
</CheckboxAndPicker>
</FormGroupComponent>
I think this is an improvement over the code in the original post because:
We have removed the need for a Form component.
We've removed the need of mapping through the children of our Form component and getting the props of the Form component's children while still within the Form component.
We are no longer attaching new props to the TimePicker component that are unused by the actual TimePicker component.
Factoring out the usage of the prop's children has made this code much more straightforward in my opinion. Therefore I think that it probably was an anti-pattern to call the props of our child components within the parent component, as it needlessly complicated the code.

React performance - DOM styling and component rendering

Can someone please explain what happens when I change the CSS class of a React component?
Let's say I have this code:
import React, {useState} from "react"
const Comp = props => {
const [class, setClass] = useState('visible')
if (props.hide) {
setClass('hidden');
}
return (<p className={class}>some text</p>)
}
My understanding of the process is this one:
Something in the parent component triggers a state change determining the Comp child to be called with property "hide" set to true
React will reconstruct the virtual DOM and the resulting html node of Comp will have the class set to "hidden"
React will update the DOM
Browser will render the DOM and apply the new styling
Am I correct?
Some related questions:
a. If the applied CSS class is just hiding the component, wouldn't be more efficient just to apply some conditional logic inside React parent component and don't display it at all?
b. If applying styling to html node just triggers the browser's painting method, why should we link CSS classes to the app state, thus triggering a full rendering? For me it seems inefficient.

tab containers in ReactJS: only render selected child vs. using `display:none`

I built a simplistic tab container in ReactJS using the idea that the container component keeps in its state an integer index denoting the tab pane to display and then renders only the child (from the this.props.children array) that is found at that index position.
The gist of this approach was:
const TabContainer = React.createClass({
props: {
tabNames: React.PropTypes.arrayOf(React.PropTypes.string).isRequired
},
getInitialState: function() {
return {
activeIndex: 0
};
},
setTab: function(n) {
this.setState({activeIndex: n});
},
render: function render() {
const childToRender = this.props.children[this.state.activeIndex];
return (
<div className='tab-container'>
<Tabs
tabNames= {this.props.tabNames}
active = {this.state.active}
setTab = {this.setTab}
/>
<div className='tab-pane'>
{childToRender}
</div>
</div>
);
}
});
I.e. only the indexed child is selected and rendered (I've omitted for the sake of simplicity the code handling the edge case where this.props.children is not an Array).
I found out that this approach was unsatisfactory as when the user selected different tabs, the component corresponding to the pane to render was mounted and unmounted repeatedly and any state that the panes had could not be preserved.
Ultimately, I used an approach in which all children are rendered and all panes, except the selected one, are assigned a class hidden that is then used to style those panes as: display:none. When this later solution was used my panes remained mounted even after the user clicked through the various tabs and any state they had wasn't lost as the user cycled through the tabs.
My questions are:
was the initial approach (where only a specific child is rendered) an anti-pattern or was there some other mechanism I could have used to preserve the state of the individual panes or prevent them from being unmounted?
if the initial approach was indeed an anti-pattern how can that anti-pattern be expressed more generally?
I don't think the initial approach was an antipattern at all. Choosing whether or not to mount/unmount in your logic is just dependent on your circumstances. If you want state preserved, then don't unmount. If you want a fresh element, complete with a call to getInitialState, then unmounting is the right way to go.
As an easy counterexample, consider React-Router. The Router completely unmounts/remounts components on route change. And route changing is effectively just a higher order of tabbing.
But given the situation where you want state preserved, I think your solution is the proper one. You might want to take a look at material-ui, which does something very similar in their tabbing: https://github.com/callemall/material-ui/blob/master/src/Tabs/TabTemplate.js

Categories