React functional component stops rendering on state changes after remounting - reactjs

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

Related

setState triggers child UseEffect with props as dependency

I have a component which receives as a prop a setState hook from its parent component. When it calls that prop causes a re-render since parent updates its state.
The thing is that I have a useEffect inside the child component that needs to be run only when props change (props is the only dependency it has). But since its parent re-renders it will be executed even when it's not need and I don't know a way to prevent it. I think parent rerender send the props again to the child.
Attaching a basic reproduction of my issue
function Parent = () => {
const [test, setTest] = useState()
return (
<Child onClick={value => setTest(value)} propINeed={{foo: 'bar'}}/>
)
}
function Child = props => {
useEffect(() => {
//STUFF I NEED TO RUN ONLY WHEN PROPS DO REALLY CHANGE
}, [props])
//ONCLICK PROP IS CALLED, AND useEffect RUNS AGAIN, messing with my data
}
I know props don't change. So I'm pretty sure it's due to the parent re-render
Put only the propINeed into the dependency array, so the useEffect callback doesn't run on click:
useEffect(() => {
//STUFF I NEED TO RUN ONLY WHEN PROPS DO REALLY CHANGE
}, [props.propINeed])

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

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.

React Hoooks: change children state from parent

I have a problem to change state for a child from the parent component.
Here is a sample sandbox.
https://codesandbox.io/s/parent-child-communication-hooks-34tjn?file=/src/components/Child.jsx
Here is my goal: When a user clicks "Turn on all button" in the Parent component, All of the children components will be turned on together. Turn off will turn off all children.
But Still, It is necessary to control each child.
Hi and welcome to StackOverflow. I see in your CodeSandbox that you started using React Hooks. Other than the useState hook we have the useEffect which will be triggered after the dependency has changed. You can achieve that by using this hook.
Simply add a new prop to your Child Component, this prop will contain the state value for the parent, we will call this parentValue. This is your Parent Component
//Parent.jsx
//your code
{childrentdata.map(x => (
<ChildComponent
name={x.name}
buttonOn={state.ParentOn}
parentValue={state.ParentOn}
/>
))}
Then, in your Child Component add the hook
useEffect(() => {
setState(prevState => ({ ...prevState, buttonOn: props.parentValue }));
}, [props.parentValue]);
The hook will watch for props and state changes that we specify in the array dependency as the second argument. So, everytime the parent toggle changes, it will change all the children. There is a change I made, if you notice I'm using a callback inside your setState, this is the way we can get the actual value for the state without getting a stale value. So, is a good practice to use this callback to prevent side effects.

Can't perform a React State update on unMounted child component?

Am getting this warning:
Can't perform a React state update on unmounted component. This is a no-op...
It results from a child component and I can't figure out how to make it go away.
Please note that I have read many other posts about why this happens, and understand the basic issue. However, most solutions suggest cancelling subscriptions in a componentWillUnmount style function (I'm using react hooks)
I don't know if this points to some larger fundamental misunderstanding I have of React,but here is essentially what i have:
import React, { useEffect, useRef } from 'react';
import Picker from 'emoji-picker-react';
const MyTextarea = (props) => {
const onClick = (event, emojiObject) => {
//do stuff...
}
const isMountedRef = useRef(true);
useEffect(() => {
isMountedRef.current = true;
});
useEffect(() => {
return () => {
console.log('will unmount');
isMountedRef.current = false;
}
});
return (
<div>
<textarea></textarea>
{ isMountedRef.current ? (
<Picker onEmojiClick={onClick}/>
):null
}
</div>
);
};
export default MyTextarea;
(tl;dr) Please note:
MyTextarea component has a parent component which is only rendered on a certain route.
Theres also a Menu component that once clicked, changes the route and depending on the situation will either show MyTextarea's parent component or show another component.
This warning happens once I click the Menu to switch off MyTextarea's parent component.
More Context
Other answers on StackOverflow suggest making changes to prevent state updates when a component isn't mounted. In my situation, I cannot do that because I didn't design the Picker component (rendered by MyTextarea). The Warning originates from this <Picker onEmojiClick={onClick}> line but I wouldn't want to modify this off-the-shelf component.
That's explains my attempt to either render the component or not based on the isMountedRef. However this doesn't work either. What happens is the component is either rendered if i set useRef(true), or it's never rendered at all if i set useRef(null) as many have suggested.
I'm not exactly sure what your problem actually is (is it that you can't get rid of the warning or that the <Picker> is either always rendering or never is), but I'll try to address all the problems I see.
Firstly, you shouldn't need to conditionally render the <Picker> depending on whether MyTextArea is mounted or not. Since components only render after mounting, the <Picker> will never render if the component it's in hasn't mounted.
That being said, if you still want to keep track of when the component is mounted, I'd suggest not using hooks, and using componentDidMount and componentWillUnmount with setState() instead. Not only will this make it easier to understand your component's lifecycle, but there are also some problems with the way you're using hooks.
Right now, your useRef(true) will set isMountedRef.current to true when the component is initialized, so it will be true even before its mounted. useRef() is not the same as componentDidMount().
Using 'useEffect()' to switch isMountedRef.current to true when the component is mounted won't work either. While it will fire when the component is mounted, useEffect() is for side effects, not state updates, so it doesn't trigger a re-render, which is why the component never renders when you set useRef(null).
Also, your useEffect() hook will fire every time your component updates, not just when it mounts, and your clean up function (the function being returned) will also fire on every update, not just when it unmounts. So on every update, isMountedRef.current will switch from true to false and back to true. However, none of this matters because the component won't re-render anyways (as explained above).
If you really do need to use useEffect() then you should combine it into one function and use it to update state so that it triggers a re-render:
const [isMounted, setIsMounted] = useState(false); // Create state variables
useEffect(() => {
setIsMounted(true); // The effect and clean up are in one function
return () => {
console.log('will unmount');
setIsMounted(false);
}
}, [] // This prevents firing on every update, w/o it you'll get an infinite loop
);
Lastly, from the code you shared, your component couldn't be causing the warning because there are no state updates anywhere in your code. You should check the picker's repo for issues.
Edit: Seems the warning is caused by your Picker package and there's already an issue for it https://github.com/ealush/emoji-picker-react/issues/142

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