Does React know not to re-render child when parent changes? - reactjs

<Parent P={some_state} >
<Child />
<Parent />
If some_state changes, causing the Parent to re-render, does it know to leave Child alone?

React Memo
I think what you are looking for is React.memo. Memoizing a component tells the component not to change or render unless the props it cares about changes or it has it's own internal changes.
In your example, if someState changes in the <Parent /> the <Child /> will render again. To fix that we can memoize our child.
Example Implementation
// Child Component
import React, { useEffect } from "react";
const Child = () => {
useEffect(() => console.log("Child rendered")); // simple way of logging each time the component renders
return (
<span>
I take no props, I shouldn't update unless my local state changes
</span>
)
};
const MemomizedChild = React.memo(Child);
// Parent Component
const Parent = ({ someState }) => {
return (
<MemomizedChild />
)
};
// Parent of Parent Component
import { useEffect, useState } from "react";
const App = () => {
const [foobar, setFoobar] = useState("hello");
useEffect(() => {
setTimeout(() => setFoobar("world"), 5000)
}, []) // after 5 seconds change the state variable to "world"
return (
<Parent someState={foobar} />
)
}
Explanation
Here are the steps or chain of events in that example:
App component has a local state variable foobar (just to trigger a prop change in the Parent component)
After five seconds the Parent's someState will change
Normally would trigger a render from the Child
We memoized Child so we should only see that "Child rendered" console log once
If we use the non memoized Child component the "Child rendered" console log will fire twice, once on mount and the second time when the Parent props change.

Related

How to call a function immediately

I write my request in my parent Component with code below:
useEffect(() => {
// my request
}, [])
As we all know, useEffect of parent component will implement after child component, so this request will be implemented after all child components rendered.
How can I call my request immediately before my child component's useEffect?
The order of execution of useEffect in a simple parent child component like this
<Parent>
<Child />
</Parent>
is, child useEffect first and then parent's.
If you want child to wait for parent's useEffect, you can use a conditional to show something else till then:
const [showChild, setShowChild] = useState(false);
useEffect(() => {
/* other activies */
setShowChild(true);
});
return (
<>
<otherСomponents />
{showChild && <Child />
<otherСomponents / >
</>
);
Don't put you function into useEffect, just in the component body:
export default function YourComponent() {
// Your logic here
return (
...
);
}
Because your component is just a function too, your function will be called immediately.
Simply, put statement of parent useEffect to top of child component's useEffect.

How to pass data from child component to parent component without the child component rerendering in React?

My child component is an accordion with controls in it. The problem is, everytime you input data on the controls and it triggers an onChange event which passes its data to its parent component, the accordion closes/collapses and I don't need that behavior.
The accordion is just imported from a library and I can't change its behavior.
Is there anyway to prevent this or pass props/data from child component to parent component without rerendering/restarting DOM of child component?
Parent component:
const ParentComponent = () => {
const handleChangeChildAccordion = (e) => {
console.log(e);
}
return (
<ChildComponentAccordion currentData={currentData} onChangeTPAccordion={handleChangeChildAccordion}/> }
);
}
Child Component:
const ChildComponent = (props) => {
const onDataChangeHandler = (e) => {
props.onChangeTPAccordion(e);
};
return (
<Accordion onChange={onDataChangeHandler}/>
);
}
The child component doesn't remount, it rerenders. Normally, a component rerenders any time any its parent rerenders. To avoid that you:
Avoid changing the child component's props, and
Memoize the child component so it doesn't rerender when its props don't change in a way it cares about.
You do the first by ensuring that, for instance, callbacks you pass the component are stable (they don't change on every render). In a function component, you do it by using useMemo, useCallback, and/or useRef. In a class component, you typically do that by making the callbacks properties on the component instance that aren't recreated.
You do the second by using React.memo (if it's a function component) or for a class component by extending PureComponent (for simple memoization) or implementing shouldComponentUpdate.
You haven't given us much code to work with, so here's a simple example:
const { useState, useCallback, useRef } = React;
const log = (...msgs) => console.log(...msgs);
const Child1 = React.memo(({value, doUpdate}) => {
log(`Child1 rendering`);
return <div>
Child1 - Value: {value} <input type="button" value="+" onClick={doUpdate} />
</div>;
});
const Child2 = React.memo(({value, doUpdate}) => {
log(`Child2 rendering`);
return <div>
Child2 - Value: {value} <input type="button" value="+" onClick={doUpdate} />
</div>;
});
const Parent = () => {
log(`Parent rendering`);
const [counter1, setCounter1] = useState(0);
const [counter2, setCounter2] = useState(0);
// `useCallback` remembers the first function you pass it and returns
// it to you, only returning the new one you pass it if the
// dependencies array changes. This is one way to create a stable
// callback.
const update1 = useCallback(() => setCounter1(c => c + 1), []);
// `useRef` gives you an object you can put arbitrary properties on,
// which is another way to have a stable callback.
const update2Ref = useRef(null);
if (!update2Ref.current) {
update2Ref.current = () => setCounter2(c => c + 1);
}
const update2 = update2Ref.current;
return <div>
<Child1 value={counter1} doUpdate={update1} />
<Child2 value={counter2} doUpdate={update2} />
</div>;
};
ReactDOM.render(<Parent />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
TypeScript playground with types added
That shows two different ways of making acallback stable (useCallback and useRef). (useMemo is what useCallback uses under the hood, so I didn't do an example of it.)
In that example, notice how the child telling the parent to update its counter doesn't cause the other child to re-render. That's because:
We don't change the other child's props (because our callbacks are stable and the other child's counter didn't change), and
We've memoized the child components
You can customize whether the component re-renders by passing a second argument to React.memo which is a function that says whether the new props coming in are "the same" (for rendering purposes) as the previous ones. By default, React.memo does a simple === comparison on each prop. And again, for class components, PureComponent does the simple === comparison, or you can implement shouldComponentUpdate yourself for more finely-tuned checks.

React useCallback() function in Parent is only working fine with Child if React.memo is used, without it not working

Parent Component
The parent component contains input which on change sets the state "local" and a button which on click takes this "local" state's value and sets it to "sendLocal"
Functions
changehandler : triggers on input type change.
sendLocalValue : takes "local" value puts it into "sendLocal" variable triggers on button click.
sendValue : this memoized function with dependeny "sendLocal" is passed on as a prop in child component triggers once the child is rendered.
import React, { useState, useCallback } from "react";
import ChildComponent from "./ChildComponent";
function ParentComponent() {
const [local, setLocal] = useState();
const [sendLocal, setsendLocal] = useState();
const changehandler = (e) => {
console.log("parent");
setLocal(e.target.value);
};
const sendLocalValue = () => {
setsendLocal(local);
};
const sendValue = useCallback(() => {
return sendLocal;
}, [sendLocal]);
return (
<>
<input type="text" onChange={changehandler} defaultValue={local} />
<button onClick={sendLocalValue}>send</button>
<ChildComponent getValue={sendValue} />
</>
);
}
export default ParentComponent;
Child Component
getValue prop calls the memoized "sendValue" function of parent which returns the value of sendLocal.
Problem
Everything works fine,the child component renders only when the "sendLocal" value changes on button click but if i remove React.memo() in child both the component render on input type change even with useCallback() used, why?
import React, { useEffect, useState } from "react";
function ChildComponent({ getValue }) {
console.log("child");
return <div>child's {getValue()}</div>;
}
export default React.memo(ChildComponent);
There is a general misconception that React components rerender when props or state change. while this is partially true, can lead to misunderstoods: a react component rerenders when its state changes or when its parent rerenders (because its state changed or because its parent rerendered, and so on).
So this means that every time ParentComponent rerenders, all its (non memoized) children will rerender.
To avoid this, you can use React.memo or React.PureComponent.
You can verify that by removing React.memo and not passing any props to the ChildComponent. Even with no props, it will rerender every time its parent rerenders.
when the parent re-renders, the whole props object for the child is created as a new object, which leads to the child re-render.
it does not matter that you have memoized all the different individual props, you have to memoize the component itself so that it does not re-render when the whole props object changes.
In this case, it also means that you still have to memoize the function as well as the child component, because React.memo only does a shallow comparison of individual props.

Unmounting a component when triggered through parent component

I have a parent component and child component. Child component will be rendered through parent based on a condition. So, when child gets rendered, all useEffect and other code gets executed and I'm able to achieve componentDidMount kind of methods. I'm not directly accessing child component but only use Parent to render it based on parent's props.
Once my child component is visible, I call a method which resets 'doesChildExists' prop and re-render ParentComponent and since the condition is failed, ChildComponent is not rendered (unmounted). Once, ChildComponent is about to unmount, I need to do some stuff, but existing useEffect doesn't seem to get called. How do I handle this? Please help!!
Please, let me know if this is not the right approach, and I have handle rendering of child in a different way.
const ChildComponent: FunctionComponent<Props> = (props: Props) => {
useEffect(() => {
return () => {
// handle unmount
}
}, []);
return (
<div class='textDiv'>
This is content
</div>
)
}
const ParentComponent: FunctionComponent<ParentProps> = (props: ParentProps) => {
return (
<>
{doesChildExists &&
createPortal(<ChildComponent />, document.body)}
</>
)
}

Find out from parent component that child component is mounted in react

I have a react component, which has a child component. I have to know in parent component that the child component is mounted. How to do that?
This depends on the version of React you are using.
If you are using React version 16.8 or higher, you can use the useEffect hook in the child component which can trigger a function in the parent component.
const Parent = props => {
const OnChildMount = () => {
console.log('Child Mounted);
}
return (
<Child onMount={OnChildMount}/>
)
}
const Child = props => {
useEffect(() => {
props.onMount();
}, []);
return (
//The child JSX
);
}
The empty brackets [] in the useEffect hook makes sure that it only runs when the Child component is mounted.
If you are using older versions of React, you can use the ComponentDidMount lifecycle hook in the child component to trigger the same function in the Parent.
This being said, the final solution would actually depend on your exact requirement.

Resources