React child component re-renders even when props do not change - reactjs

I am trying to create a chart in D3 inside a React component. Whenever I change the state in my parent component, the child gets re-rendered even though it's props do not change or are affected in any way by the setState. I have even tried to use React.memo on the child component but it still gets re-rendered. How can I avoid the re-render if the props have not changed.
PSEUDOCODE
/* Parent component */
function parentComponent(props) {
const [a, setA] = useState({})
function customEventHandler() {
setA(); // This cause child to re-render
}
return (
// Some rendering based on the value of a
// Some custom event handler
<ChildComponent config={{}} />
)
}
/* Child component [D3 based component with useRef] */
function childComponent = React.memo((props) => {})
CodeBox link here https://codesandbox.io/s/patient-fast-unlzq?file=/src/App.js
ISSUE: The graphs get re-rendered every time the input text changes.

Related

React functional component stops rendering on state changes after remounting

Any suggestions with this strange situation would be greatly appreciated.
I have a parent component that listens to the callback status from a child component, and renders a progress indicator based on the status. The child component is an editor and uses memo to prevent re-rendering. This is required by the editor package. Everything works fine when the parent and child are fist mounted, but if the parent is unmounted and remounted, it doesn't render the state changes triggered by the child callback.
I've tried various things to force a re-rendering, but nothing seems to work. I added a counter to the state and a delay on updating the state. I use ref as a value since the state is being accessed in a closure.
The child component is an editor that listens to websocket information and calls handleStatus callback asynchronously. The child component is working fine after the remount and sending the updates to the handleStatus callback.
Parent Component:
const [isConnected, setIsConnected] = useState<number>(0);
const forceUpdate = useRef<number>(0);
const statusRef = useRef('connected')
const handleStatus = (status: string) => {
if ('connected' !== status) {
forceUpdate.current += 1;
setTimeout(()=>{
setIsConnected(forceUpdate.current);
}, 100)
statusRef.current = status;
}
}
<div style={{width: '100%'}} >
{statusRef.current === 'connected' ? null : <CircularProgress />}
<ChildComponent key={roomId} roomId={roomId} handleStatus={handleStatus}/>
</div>
From the debugger, I can see that handleStatus is being called and setIsConnected is being called with a new value. But the parent does not render when the state is changed.
Again, the parent only stops listening to the state updates after it was remounted. On the first mount, the parent renders on every state update.
Any suggestions where to look would be greatly appreciated.
Edit:
I'm using the same key for the parent component, so react should remount the existing component instead of creating a new component. From what I can see, it does look like it's remounting the same component since the ref retains its value and the callback from the child component is still valid. The state value is stale, but that's expected since it's in a closure. The only thing that's not making sense is that I can't seem to trigger a rerender by updating the setState. I even tried a state function incase the setState was getting stale too, but that didn't work either.
you need to use useEffect and as dependency put statusRef variable
useEffect(() => {
}, [statusRef])
<ChildComponent key={roomId} roomId={roomId} handleStatus={handleStatus}/>

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.

How to make functional component rerender properly on parents state chage?

please help me figure out why the component works like that.
I have a functional component something like this:
function MyComponent(props) {
const { dataArr } = props;
[ownDataArr, setOwnDataArr] = useState([...dataArr]);
// doesn't change after changing in the state of the parent component
console.log(ownDataArr);
return (
// ownDataArr is used here
);
It receives dataArr from parent component via props (parent component contains this in state). And when changed in parent component after MyComponent rerenders, ownDataArr stays the same. What am I doing wrong?
P.S. The child component needs the state, since it must be able to change the received data without constantly sending it to the parent.
You can do this to update the state on props change
useEffect(() => {
setOwnDataArr(dataArr)
}, [dataArr])
This is because state initialize on the first render of component with the props and when the props change, we have to update the state using useEffect
function MyComponent(props) {
const { dataArr } = props;
[ownDataArr, setOwnDataArr] = useState([...dataArr]);
// add useEffect wich depend on dataArr changing,
// because initial state is memoized by useState
useEffect(() => setOwnDataArr(dataArr), [dataArr])
return (
// ownDataArr is used here
);
}

Prevent child from rerender when it updates parents state using parent method as prop in react native

I have a parent component and two child components. so i want to update second component when some action is performed in first component. Say I have Component1 and Component2, and a ParentComponent, so when I change state of ParentComponent from Component1 it changes the state in Component2 but also rerenders component1. I dont want to rerender component1. Tried to use callback but didnot help.
You can use memo to handle this scenario:
const MyComponent = React.memo(function MyComponent(props) {
/* only rerenders if props change */
});
Such component will only be re-rendered when there is a change in its own state or props.

How do I force a child component to rerender when given new props values?

I have tried this pattern.
ParentComponent
...
render(
return <ChildComponent newProps="newPropsValue />)
ChildComponent
...
ComponentWillReceiveProps{
this.setState({"propsKey": "newPropsValue"})
}
As far as I understand the initial component rendering is triggered by the props change, and as setState is asynchronous (for some reason), the rendering with the new state update is not done on the first pass.
However what I don't understand is why when it finally decides to update the state, it doesn't rerender the component. I thought state changes that are caused by setState always trigger a rerender.
So in the end I have a component that uselessly rerenders before the state is actually changed, and then does nothing when/if(?) the state is updated. I don't understand this behaviour at all.
setState will trigger componentUdpate -> componentWillUpdate -> render. props change will trigger componentWillReceiveProps before this chain. You can have a look here at this image about React lifecycle. You can see the different how React behave on props and state.
So:
However what I don't understand is why when it finally decides to update the state, it doesn't re-render the component.
Updating state by setState will trigger the render function (re-render). And props also trigger render as well.
Following your code:
componentWillReceiveProps:
this.props.newProps="newPropsValue"
this.state.propsKey="newPropsValue"
render: as above, nothing change.
If any event of childComponent setting propsKey by setState (onClick, onChange ...). Assuming setState({propsKey: "anotherValue"}). Then render will be triggered again with this.state.propsKey="anotherValue and this.props.newProps="newPropsValue"
Now let's update your childComponent's props within parentComponent, assuming newProps="latestPropsValue":
Before componentWillReceiveProps:
this.props.newProps="latestPropsValue"
this.state.propsKey="anotherValue"
After componentWillReceiveProps:
this.props.newProps="latestPropsValue"
this.state.propsKey="latestPropsValue"
How do I force a child component to rerender when given new props values?
If your render is using state then setState inside render. And if you are using props inside render, it also being updated accordingly
I have found a nice solution using key attribute. If we changed key property of a child component or some portion of React Component, it will re-render entirely. It will use when you need to re-render some portion of React Component or re-render a child component depending on props or state. Here is a example. I will re-render the full component.
import React, { useState, useEffect } from "react";
import { PrEditInput } from "./shared";
const BucketInput = ({ bucketPrice = [], handleBucketsUpdate, mood }) => {
const data = Array.isArray(bucketPrice) ? bucketPrice : [];
const [state, setState] = useState(Date.now());
useEffect(() => {
setState(Date.now());
}, [mood, bucketPrice]);
return (
<span key={state}>
{data.map((item) => (
<PrEditInput
key={item.id}
label={item?.bucket?.name}
name={item.bucketId}
defaultValue={item.price}
onChange={handleBucketsUpdate}
mood={mood}
/>
))}
</span>
);
};
export default BucketInput;

Resources