Unmounting a component when triggered through parent component - reactjs

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)}
</>
)
}

Related

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.

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

<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.

How to pass props from functional component to class component

I'm quite new to functional components and I've been on this for hours.
I simply want to pass a prop from a parent functional component to a class child component so that I will use the updated prop (which will be a boolean) to change the display of an element. That's all.
In this scenario, I want to switch the sidenav from opened to closed based on the prop.
Here is the parent component (functional):
let opened = false;
function openSidenav(){
opened = !opened;
}
const [sidebarOpened, setOpened ] = useState(opened);
return (
<Sidebar sidebarOpened={opened} />
)
and the child component (class):
componentDidUpdate(prevProps) {
if(this.props.sidebarOpened !== prevProps){
this.setState({ sidebar: true});
}
}
It's just not working, the child component isn't receiving changes, I guess I didn't pass the prop rightly. I know the code just needs correction but I don't know where I'm not getting it.
Thank you.
The argument to useState is only used once. You need to set the state in openSidenav function to trigger a re-render.
Parent
function openSidenav(){
setOpened(prev => !prev);
}
const [sidebarOpened, setOpened ] = useState(false);
return (
<Sidebar sidebarOpened={sidebarOpened} />
)
Also in child component, use prevProps.sidebarOpened (not just prevProps).
Child
componentDidUpdate(prevProps) {
if(this.props.sidebarOpened !== prevProps.sidebarOpened){ //<---- see here
this.setState({ sidebar: this.props.sidebarOpened});//<---- see here
}
}
p.s - Also, since you are using props directly in child component, you can consider to not to copy props into state.
In parent,
const [sidebarOpened, setOpened ] = useState(false);
function openSidenav(){
setOpened(!sidebarOpened);
}
return (
<Sidebar sidebarOpened={sidebarOpened} />
)
And in child component class directly use this.props.sidebarOpened instead of copying over the prop to state. If you intend to edit the value of sidebarOpened in the child component, pass setOpened to the child component as a prop and use that to edit the value.

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.

How the render process is going in React?

I just starting to get confused about the render process in React.
Let's say I've got a Higher Order Component what looks something like this:
const withHOC = () => WrapperComponent => {
return class WithHOC extends React.Component {
render() {
//someProps => what will change...
//...rest => won't change
const { someProps, ...rest } = this.props
return(
<WrapperComponent {...rest} />
)
}
}
}
This is where I am quite confused...
as someProps will change the HOC itself will re-render. Is this means the WrappedComponent will be re-rendered as well?
I mean the WrappedComponent's props not changing.
And how expensive can this be?
React HOC is not a big deal to understand when you think it as just a parent component.
Now, guess what happens when child component receive the props from the parent component?
The child component will be re-rendered.
What happens to the parent component render?
The parent component is also re-rendered because its props changes.
The same thing applies to the HOC. Thus, WrapperComponent will also re-render whenever the props / state changes.

Resources