React: why child component doesn't update when prop changes - javascript
Why in the following pseudo-code example Child doesn't re-render when Container changes foo.bar?
Container {
handleEvent() {
this.props.foo.bar = 123
},
render() {
return <Child bar={this.props.foo.bar} />
}
Child {
render() {
return <div>{this.props.bar}</div>
}
}
Even if I call forceUpdate() after modifying the value in Container, Child still shows the old value.
Update the child to have the attribute 'key' equal to the name. The component will re-render every time the key changes.
Child {
render() {
return <div key={this.props.bar}>{this.props.bar}</div>
}
}
Because children do not rerender if the props of the parent change, but if its STATE changes :)
What you are showing is this:
https://facebook.github.io/react/tips/communicate-between-components.html
It will pass data from parent to child through props but there is no rerender logic there.
You need to set some state to the parent then rerender the child on parent change state.
This could help.
https://facebook.github.io/react/tips/expose-component-functions.html
I had the same problem.
This is my solution, I'm not sure that is the good practice, tell me if not:
state = {
value: this.props.value
};
componentDidUpdate(prevProps) {
if(prevProps.value !== this.props.value) {
this.setState({value: this.props.value});
}
}
UPD: Now you can do the same thing using React Hooks:
(only if component is a function)
const [value, setValue] = useState(propName);
// This will launch only if propName value has chaged.
useEffect(() => { setValue(propName) }, [propName]);
When create React components from functions and useState.
const [drawerState, setDrawerState] = useState(false);
const toggleDrawer = () => {
// attempting to trigger re-render
setDrawerState(!drawerState);
};
This does not work
<Drawer
drawerState={drawerState}
toggleDrawer={toggleDrawer}
/>
This does work (adding key)
<Drawer
drawerState={drawerState}
key={drawerState}
toggleDrawer={toggleDrawer}
/>
Obey immutability
Quite an old question but it's an evergreen problem and it doesn't get better if there are only wrong answers and workarounds.
The reason why the child object is not updating is not a missing key or a missing state, the reason is that you don't obey the principle of immutability.
It is the aim of react to make apps faster and more responsive and easier to maintain and so on but you have to follow some principles. React nodes are only rerendered if it is necessary, i.e. if they have updated. How does react know if a component has updated? Because it state has changed. Now don't mix this up with the setState hook. State is a concept and every component has its state. State is the look or behaviour of the component at a given point in time. If you have a static component you only have one state all the time and don't have to take care of it. If the component has to change somehow its state is changing.
Now react is very descriptive. The state of a component can be derived from some changing information and this information can be stored outside of the component or inside. If the information is stored inside than this is some information the component has to keep track itself and we normally use the hooks like setState to manage this information. If this information is stored outside of our component then it is stored inside of a different component and that one has to keep track of it, its theirs state. Now the other component can pass us their state thru the props.
That means react rerenders if our own managed state changes or if the information coming in via props changes. That is the natural behaviour and you don't have to transfer props data into your own state.
Now comes the very important point: how does react know when information has changed? Easy: is makes an comparison! Whenever you set some state or give react some information it has to consider, it compares the newly given information with the actually stored information and if they are not the same, react will rerender all dependencies of that information.
Not the same in that aspect means a javascript === operator.
Maybe you got the point already.
Let's look at this:
let a = 42;
let b = a;
console.log('is a the same as b?',a === b); // a and b are the same, right? --> true
a += 5; // now let's change a
console.log('is a still the same as b?',a === b); // --> false
We are creating an instance of a value, then create another instance, assign the value of the first instance to the second instance and then change the first instance.
Now let's look at the same flow with objects:
let a = { num: 42};
let b = a;
console.log('is a the same as b?',a === b); // a and b are the same, right? --> true
a.num += 5; // now let's change a
console.log('is a still the same as b?',a === b); // --> true
The difference this time is that an object actually is a pointer to a memory area and with the assertion of b=a you set b to the same pointer as a leading to exactly the same object.
Whatever you do in a can be accesed by your a pointer or your b pointer.
Your line:
this.props.foo.bar = 123
actually updates a value in the memory where "this" is pointing at.
React simply can't recognize such alterations by comparing the object references. You can change the contents of your object a thousand times and the reference will always stay the same and react won't do a rerender of the dependent components.
That is why you have to consider all variables in react as immutable. To make a detectable change you need a different reference and you only get that with a new object. So instead of changing your object you have to copy it to a new one and then you can change some values in it before you hand it over to react.
Look:
let a = {num: 42};
console.log('a looks like', a);
let b = {...a};
console.log('b looks like', b);
console.log('is a the same as b?', a === b); // --> false
The spread operator (the one with the three dots) or the map-function are common ways to copy data to a new object or array.
If you obey immutability all child nodes update with new props data.
Confirmed, adding a Key works. I went through the docs to try and understand why.
React wants to be efficient when creating child components. It won't render a new component if it's the same as another child, which makes the page load faster.
Adding a Key forces React to render a new component, thus resetting State for that new component.
https://reactjs.org/docs/reconciliation.html#recursing-on-children
According to React philosophy component can't change its props. they should be received from the parent and should be immutable. Only parent can change the props of its children.
nice explanation on state vs props
also, read this thread Why can't I update props in react.js?
You should use setState function. If not, state won't save your change, no matter how you use forceUpdate.
Container {
handleEvent= () => { // use arrow function
//this.props.foo.bar = 123
//You should use setState to set value like this:
this.setState({foo: {bar: 123}});
};
render() {
return <Child bar={this.state.foo.bar} />
}
Child {
render() {
return <div>{this.props.bar}</div>
}
}
}
Your code seems not valid. I can not test this code.
You must have used dynamic component.
In this code snippet we are rendering child component multiple time and also passing key.
If we render a component dynamically multiple time then React doesn't render that component until it's key gets changed.
If we change checked by using setState method. It won't be reflected in Child component until we change its key. We have to save that on child's state and then change it to render child accordingly.
class Parent extends Component {
state = {
checked: true
}
render() {
return (
<div className="parent">
{
[1, 2, 3].map(
n => <div key={n}>
<Child isChecked={this.state.checked} />
</div>
)
}
</div>
);
}
}
My case involved having multiple properties on the props object, and the need to re-render the Child on changing any of them.
The solutions offered above were working, yet adding a key to each an every one of them became tedious and dirty (imagine having 15...). If anyone is facing this - you might find it useful to stringify the props object:
<Child
key={JSON.stringify(props)}
/>
This way every change on each one of the properties on props triggers a re-render of the Child component.
Hope that helped someone.
I have the same issue's re-rendering object props, if the props is an object JSON.stringify(obj) and set it as a key for Functional Components. Setting just an id on key for react hooks doesn't work for me. It's weird that to update the component's you have to include all the object properties on the key and concatenate it there.
function Child(props) {
const [thing, setThing] = useState(props.something)
return (
<>
<div>{thing.a}</div>
<div>{thing.b}</div>
</>
)
}
...
function Caller() {
const thing = [{a: 1, b: 2}, {a: 3, b: 4}]
thing.map(t => (
<Child key={JSON.stringify(t)} something={thing} />
))
}
Now anytime the thing object changes it's values on runtime, Child component will re-render it correctly.
You should probably make the Child as functional component if it does not maintain any state and simply renders the props and then call it from the parent. Alternative to this is that you can use hooks with the functional component (useState) which will cause stateless component to re-render.
Also you should not alter the propas as they are immutable. Maintain state of the component.
Child = ({bar}) => (bar);
export default function DataTable({ col, row }) {
const [datatable, setDatatable] = React.useState({});
useEffect(() => {
setDatatable({
columns: col,
rows: row,
});
/// do any thing else
}, [row]);
return (
<MDBDataTableV5
hover
entriesOptions={[5, 20, 25]}
entries={5}
pagesAmount={4}
data={datatable}
/>
);
}
this example use useEffect to change state when props change.
I was encountering the same problem.
I had a Tooltip component that was receiving showTooltip prop, that I was updating on Parent component based on an if condition, it was getting updated in Parent component but Tooltip component was not rendering.
const Parent = () => {
let showTooltip = false;
if(....){ showTooltip = true; }
return(
<Tooltip showTooltip={showTooltip}></Tooltip>
)
}
The mistake I was doing is to declare showTooltip as a let.
I realized what I was doing wrong I was violating the principles of how rendering works, Replacing it with hooks did the job.
const [showTooltip, setShowTooltip] = React.useState<boolean>(false);
define changed props in mapStateToProps of connect method in child component.
function mapStateToProps(state) {
return {
chanelList: state.messaging.chanelList,
};
}
export default connect(mapStateToProps)(ChannelItem);
In my case, channelList's channel is updated so I added chanelList in mapStateToProps
In my case I was updating a loading state that was passed down to a component. Within the Button the props.loading was coming through as expected (switching from false to true) but the ternary that showed a spinner wasn't updating.
I tried adding a key, adding a state that updated with useEffect() etc but none of the other answers worked.
What worked for me was changing this:
setLoading(true);
handleOtherCPUHeavyCode();
To this:
setLoading(true);
setTimeout(() => { handleOtherCPUHeavyCode() }, 1)
I assume it's because the process in handleOtherCPUHeavyCode is pretty heavy and intensive so the app freezes for a second or so. Adding the 1ms timeout allows the loading boolean to update and then the heavy code function can do it's work.
You can use componentWillReceiveProps:
componentWillReceiveProps({bar}) {
this.setState({...this.state, bar})
}
Credit to Josh Lunsford
Considering the rendering limitations with props and the gains we have with states, if you use reaction hooks, there are a few tricks you can use. For example, you can convert props to state manually using useEffect. It probably shouldn't be the best practice, but it helps in theses cases.
import { isEqual } from 'lodash';
import { useEffect, useState } from 'react';
export const MyComponent = (props: { users: [] }) => {
const [usersState, setUsersState] = useState([]);
useEffect(() => {
if (!isEqual(props.users, usersState)) {
setUsersState(props.users);
}
}, [props.users]);
<OtherComponent users={usersState} />;
};
Related
Most React idiomatic way of updating state in sibling component without updating parent component
So to do a very simplified use case, I have three components: const ParentComponent = (props) => { return( <ChildComponentA ...variousPropsHere /> <ChildComponentB ...variousPropsHere /> } ChildComponentA is an overlay that shows when a user clicks something, we'll call the value "value". ChildComponentB doesn't need "value" at all. And in fact I want to prevent ChildComponentB from re-rendering based on the data change, because rendering ChildComponentB is extremely expensive, and it has no need of updating. But ChildComponentA does need the value and needs to re-render based on "value" updating. I have considered useContext, refs, useReducer, etc, but none of them are working quite how I want - useContext seems like it will update all components that subscribe to it - plus the state would still be managed in the parent, necessitating a re-render there too. Refs won't re-render ChildComponentA. useReducer also seems like it will cause parent re-render (and thus child re-renders). Memoization helps with the speed of ChildComponentB re-rendering, but it's still pretty slow. At this point I'm leaning towards just using my global redux store, storing the value there, and clearing it out on component dismount. That would allow me to update the value from ChildComponentB, without subscribing ChildComponentB (or ParentComponent) to the value itself, preventing re-render in these very expensive components. Is there a more elegant way to do this than the way I am describing? I just really cannot have ChildComponentB re-render, but must have ChildComponentA re-render based on a value change.
Instead of keeping value in Context or State of parent component, keep there callback from ChildComponentA and call it from ChildComponentB. This way only sibling component will be rerendered. Example: const ChildComponentA = () => { const context = useContext(SomeContextToStoreCallback) useEffect(() => { context.callback = (value) => { // Do something expensive } return () => { context.callback = undefined; } }, []) // rest of the code.... } const ChildComponentB = () => { const context = useContext(SomeContextToStoreCallback) const handleSomeEvent = (value) => { context.callback?.(value) } // rest of code..... } This is only a pseudo code, but you've got an idea. Something similar is used in large libraries like mui-x
Setting state causes a UI lag in React
I have a react app that renders a list of cards in a container widget. The container widget has a useEffect where we subscribe to an observable and update the array that is then used to render the cards inside the component. Each time any of the cards change, the observable emits new values resulting in creating of the array all over again and thus all the cards are re-rendered. However this re-render causes a noticeable UI lag. Here is the stripped down version of code from the container component. const obsRef = useRef<Subscription>(null); const [apiResArray, setApiResArray] = useState([]); useEffect(() => { if(obsRef.current) obsRef.current.unsubscribe(); const mySubscription = myObservable$(changingValue) .subscribe((val) => { setApiResArray(val.apiArray); }); obsRef.current = mySubscription; return () => { obsRef.current && obsRef.current.unsubscribe(); }; }, [changingValue]) return ( <CardWrapper $showMenu={showMenu}> { apiResArray.map((res, resIndex) => { return ( <Card data={{ // a json object with props }} key={res?.hKey} /> ); })} </CardWrapper> ); The Card component here simply renders the content based on props passed to it. I know that since data is an Object, referential equality may fail and I have tried memoizing the component but even that does not help. There is a lot more to these components but posting all the code won't make sense. I wish to understand what possibly might be causing the list re-render to be such a heavy operation that the whole UI gets stuck for a second or two. The array contains around only 100 objects or so. It happens whenever changingValue changes. I can share more information as required. Any suggestions on improving the performance are highly appreciated.
React will mount and unmount the component in DEV mode to validate effect cleaner and any side effect. I'm concerned about that subscribe and unsubscribe in your effect. If you want changingValue to be defined why not just wrap it inside an if statement ? if (changingValue) { const mySubscription = myObservable$(changingValue) .subscribe((val) => { setApiResArray(val.apiArray); }); obsRef.current = mySubscription; } Another observation is at render of items <Card data={ { // a json object with props } } key={res?.hKey} // Why is this a falsy value ? /> Falsy value can make key prop change those re-rendering the component, one with React internal key value and another with your implementation value
Why I am getting the old state values after the props change
I just want to understand why in the application I have following situation, below is my constructor of class component: constructor(props) { super(props); this.state = { tableAlerts: props.householdAlerts, initialAlerts: props.householdAlerts } console.log('householdAlerts', props.householdAlerts) } in render function I have: const { householdAlerts } = this.props; My issue is that in constructor I got empty array, but in render funtion I have the data. Is it possible to get the data in constructor?
This is a very bad pattern when using the class component. You are ignoring any props updates when you copy the value into state. to manage it: It requires you to manage two sources of data for the same variable: state and props. Thus, you need to add another render each time your prop change by setting it into state (don't forget to test on equality from prev and next values to avoid being in an infinite loop). You can avoid setting the state each time your props change by using the getderivedstatefromprops lifecycle method. So the recommendation is: just use the props; do not copy props into state. To learn more why you shouldn't, I highly recommend this article.
It is not recommended to set your initial component state in the constructor like so because you gonna lose the ability to use { setState } method after to update this property/state. The best practice is indeed to refer directly to the prop with { this.prop.householdAlerts }, and keep the state usage for local (or in child components} cases. if anyhow you want to store props in component state for some reason, call it in lifeCycle - componentDidMount() { const { tableAlerts, initialAlerts } = this.props; this.setState({ tableAlerts, initialAlerts }); }
Hagai Harari is right. Nevertheless, your actual problem seems to be that during your initial rendering the array is empty. Can you ensure that the array has some items, when your component is rendered for the first time? First rendering -> calls constructor <YourComponent householdAlerts={[]} /> Second rendering -> updates component <YourComponent householdAlerts={[alert1, alert2, alert3]} />
If you want initial state to have the prop value.Try something like this with 'this' keyword constructor(props) { super(props); this.state = { tableAlerts: this.props.householdAlerts, initialAlerts: this.props.householdAlerts } console.log('householdAlerts', props.householdAlerts) }
Props updates in setInterval not reflected when changing component [duplicate]
Why in the following pseudo-code example Child doesn't re-render when Container changes foo.bar? Container { handleEvent() { this.props.foo.bar = 123 }, render() { return <Child bar={this.props.foo.bar} /> } Child { render() { return <div>{this.props.bar}</div> } } Even if I call forceUpdate() after modifying the value in Container, Child still shows the old value.
Update the child to have the attribute 'key' equal to the name. The component will re-render every time the key changes. Child { render() { return <div key={this.props.bar}>{this.props.bar}</div> } }
Because children do not rerender if the props of the parent change, but if its STATE changes :) What you are showing is this: https://facebook.github.io/react/tips/communicate-between-components.html It will pass data from parent to child through props but there is no rerender logic there. You need to set some state to the parent then rerender the child on parent change state. This could help. https://facebook.github.io/react/tips/expose-component-functions.html
I had the same problem. This is my solution, I'm not sure that is the good practice, tell me if not: state = { value: this.props.value }; componentDidUpdate(prevProps) { if(prevProps.value !== this.props.value) { this.setState({value: this.props.value}); } } UPD: Now you can do the same thing using React Hooks: (only if component is a function) const [value, setValue] = useState(propName); // This will launch only if propName value has chaged. useEffect(() => { setValue(propName) }, [propName]);
When create React components from functions and useState. const [drawerState, setDrawerState] = useState(false); const toggleDrawer = () => { // attempting to trigger re-render setDrawerState(!drawerState); }; This does not work <Drawer drawerState={drawerState} toggleDrawer={toggleDrawer} /> This does work (adding key) <Drawer drawerState={drawerState} key={drawerState} toggleDrawer={toggleDrawer} />
Confirmed, adding a Key works. I went through the docs to try and understand why. React wants to be efficient when creating child components. It won't render a new component if it's the same as another child, which makes the page load faster. Adding a Key forces React to render a new component, thus resetting State for that new component. https://reactjs.org/docs/reconciliation.html#recursing-on-children
Obey immutability Quite an old question but it's an evergreen problem and it doesn't get better if there are only wrong answers and workarounds. The reason why the child object is not updating is not a missing key or a missing state, the reason is that you don't obey the principle of immutability. It is the aim of react to make apps faster and more responsive and easier to maintain and so on but you have to follow some principles. React nodes are only rerendered if it is necessary, i.e. if they have updated. How does react know if a component has updated? Because it state has changed. Now don't mix this up with the setState hook. State is a concept and every component has its state. State is the look or behaviour of the component at a given point in time. If you have a static component you only have one state all the time and don't have to take care of it. If the component has to change somehow its state is changing. Now react is very descriptive. The state of a component can be derived from some changing information and this information can be stored outside of the component or inside. If the information is stored inside than this is some information the component has to keep track itself and we normally use the hooks like setState to manage this information. If this information is stored outside of our component then it is stored inside of a different component and that one has to keep track of it, its theirs state. Now the other component can pass us their state thru the props. That means react rerenders if our own managed state changes or if the information coming in via props changes. That is the natural behaviour and you don't have to transfer props data into your own state. Now comes the very important point: how does react know when information has changed? Easy: is makes an comparison! Whenever you set some state or give react some information it has to consider, it compares the newly given information with the actually stored information and if they are not the same, react will rerender all dependencies of that information. Not the same in that aspect means a javascript === operator. Maybe you got the point already. Let's look at this: let a = 42; let b = a; console.log('is a the same as b?',a === b); // a and b are the same, right? --> true a += 5; // now let's change a console.log('is a still the same as b?',a === b); // --> false We are creating an instance of a value, then create another instance, assign the value of the first instance to the second instance and then change the first instance. Now let's look at the same flow with objects: let a = { num: 42}; let b = a; console.log('is a the same as b?',a === b); // a and b are the same, right? --> true a.num += 5; // now let's change a console.log('is a still the same as b?',a === b); // --> true The difference this time is that an object actually is a pointer to a memory area and with the assertion of b=a you set b to the same pointer as a leading to exactly the same object. Whatever you do in a can be accesed by your a pointer or your b pointer. Your line: this.props.foo.bar = 123 actually updates a value in the memory where "this" is pointing at. React simply can't recognize such alterations by comparing the object references. You can change the contents of your object a thousand times and the reference will always stay the same and react won't do a rerender of the dependent components. That is why you have to consider all variables in react as immutable. To make a detectable change you need a different reference and you only get that with a new object. So instead of changing your object you have to copy it to a new one and then you can change some values in it before you hand it over to react. Look: let a = {num: 42}; console.log('a looks like', a); let b = {...a}; console.log('b looks like', b); console.log('is a the same as b?', a === b); // --> false The spread operator (the one with the three dots) or the map-function are common ways to copy data to a new object or array. If you obey immutability all child nodes update with new props data.
According to React philosophy component can't change its props. they should be received from the parent and should be immutable. Only parent can change the props of its children. nice explanation on state vs props also, read this thread Why can't I update props in react.js?
You should use setState function. If not, state won't save your change, no matter how you use forceUpdate. Container { handleEvent= () => { // use arrow function //this.props.foo.bar = 123 //You should use setState to set value like this: this.setState({foo: {bar: 123}}); }; render() { return <Child bar={this.state.foo.bar} /> } Child { render() { return <div>{this.props.bar}</div> } } } Your code seems not valid. I can not test this code.
You must have used dynamic component. In this code snippet we are rendering child component multiple time and also passing key. If we render a component dynamically multiple time then React doesn't render that component until it's key gets changed. If we change checked by using setState method. It won't be reflected in Child component until we change its key. We have to save that on child's state and then change it to render child accordingly. class Parent extends Component { state = { checked: true } render() { return ( <div className="parent"> { [1, 2, 3].map( n => <div key={n}> <Child isChecked={this.state.checked} /> </div> ) } </div> ); } }
My case involved having multiple properties on the props object, and the need to re-render the Child on changing any of them. The solutions offered above were working, yet adding a key to each an every one of them became tedious and dirty (imagine having 15...). If anyone is facing this - you might find it useful to stringify the props object: <Child key={JSON.stringify(props)} /> This way every change on each one of the properties on props triggers a re-render of the Child component. Hope that helped someone.
I have the same issue's re-rendering object props, if the props is an object JSON.stringify(obj) and set it as a key for Functional Components. Setting just an id on key for react hooks doesn't work for me. It's weird that to update the component's you have to include all the object properties on the key and concatenate it there. function Child(props) { const [thing, setThing] = useState(props.something) return ( <> <div>{thing.a}</div> <div>{thing.b}</div> </> ) } ... function Caller() { const thing = [{a: 1, b: 2}, {a: 3, b: 4}] thing.map(t => ( <Child key={JSON.stringify(t)} something={thing} /> )) } Now anytime the thing object changes it's values on runtime, Child component will re-render it correctly.
You should probably make the Child as functional component if it does not maintain any state and simply renders the props and then call it from the parent. Alternative to this is that you can use hooks with the functional component (useState) which will cause stateless component to re-render. Also you should not alter the propas as they are immutable. Maintain state of the component. Child = ({bar}) => (bar);
export default function DataTable({ col, row }) { const [datatable, setDatatable] = React.useState({}); useEffect(() => { setDatatable({ columns: col, rows: row, }); /// do any thing else }, [row]); return ( <MDBDataTableV5 hover entriesOptions={[5, 20, 25]} entries={5} pagesAmount={4} data={datatable} /> ); } this example use useEffect to change state when props change.
I was encountering the same problem. I had a Tooltip component that was receiving showTooltip prop, that I was updating on Parent component based on an if condition, it was getting updated in Parent component but Tooltip component was not rendering. const Parent = () => { let showTooltip = false; if(....){ showTooltip = true; } return( <Tooltip showTooltip={showTooltip}></Tooltip> ) } The mistake I was doing is to declare showTooltip as a let. I realized what I was doing wrong I was violating the principles of how rendering works, Replacing it with hooks did the job. const [showTooltip, setShowTooltip] = React.useState<boolean>(false);
define changed props in mapStateToProps of connect method in child component. function mapStateToProps(state) { return { chanelList: state.messaging.chanelList, }; } export default connect(mapStateToProps)(ChannelItem); In my case, channelList's channel is updated so I added chanelList in mapStateToProps
In my case I was updating a loading state that was passed down to a component. Within the Button the props.loading was coming through as expected (switching from false to true) but the ternary that showed a spinner wasn't updating. I tried adding a key, adding a state that updated with useEffect() etc but none of the other answers worked. What worked for me was changing this: setLoading(true); handleOtherCPUHeavyCode(); To this: setLoading(true); setTimeout(() => { handleOtherCPUHeavyCode() }, 1) I assume it's because the process in handleOtherCPUHeavyCode is pretty heavy and intensive so the app freezes for a second or so. Adding the 1ms timeout allows the loading boolean to update and then the heavy code function can do it's work.
You can use componentWillReceiveProps: componentWillReceiveProps({bar}) { this.setState({...this.state, bar}) } Credit to Josh Lunsford
Considering the rendering limitations with props and the gains we have with states, if you use reaction hooks, there are a few tricks you can use. For example, you can convert props to state manually using useEffect. It probably shouldn't be the best practice, but it helps in theses cases. import { isEqual } from 'lodash'; import { useEffect, useState } from 'react'; export const MyComponent = (props: { users: [] }) => { const [usersState, setUsersState] = useState([]); useEffect(() => { if (!isEqual(props.users, usersState)) { setUsersState(props.users); } }, [props.users]); <OtherComponent users={usersState} />; };
React componentDidUpdate method won't fire on inherited props change if connected to a store that didn't change
I want my component know if some library is already loaded. To know that from any context i connect it to the "library" reducer of my store to my component. I also pass it a configuration object this.props.dataObject from the parent where the component has been called. Like this: class GoogleButton extends Component { render() { if (this.props.libraries.google) { return <a id='sharePost' className='google_icon'></a> } else { return null } } componentDidUpdate() { gapi.interactivepost.render('sharePost', this.props.dataObject) } } function mapStateToProps(state) { return { libraries: state.libraries } } export default connect(mapStateToProps)(GoogleButton) The reducer that handles the libraries state is like this: let newState = {...state} newState[action.libraryName] = action.state return newState When I change the library state componentDidUpdate works. The problem is when i change the prop inherited by the parent this.props.dataObject. In that case is where componentDidUpdate wont fire. If i remove the connect from the component it works as espected. I'm missing something here?
Most likely some of your props are mutated outside the component. For example, you might be rendering your component like this: class Parent extends Component { constructor() { super() this.state = { libraries: {} } } handleClick() { // MUTATION! this.state.libraries.google = true // Normally this forces to update component anyway, // but React Redux will assume you never mutate // for performance reasons. this.setState({ libraries: this.state.libraries }) } render() { return ( <div onClick={() => this.handleClick()}> <GoogleButton libraries={this.state.libraries} /> </div> ) } } Because Redux apps deal with immutable data, connect() uses shallow equality check for its props to avoid unnecessary re-renders. However, this won’t work if you use mutation in your app. You have two options: Don’t Mutate Anything This is the best option. For example, instead of something like handleClick() { this.state.libraries.google = true this.setState({ libraries: this.state.libraries }) } you can write handleClick() { this.setState({ libraries: { ...this.state.libraries, google: true } }) } This way we are creating a new object so connect() wouldn’t ignore the changed reference. (I’m using the object spread syntax in this snippet.) Disable Performance Optimizations A worse alternative is to completely disable performance optimizations made by connect(). Then your props would update even if you mutate them in the parent, but your app will be slower. To do this, replace export default connect(mapStateToProps)(GoogleButton) with export default connect(mapStateToProps, null, null, { pure: false })(GoogleButton) Don’t do this unless absolutely necessary.
I solved it. I'm not 100% sure that this is accurate, but I will explain. If im wrong with something, please correct me. I keep thinking about the shallow equality check that Dan said in his answer. The problem was there. I was passing down an object from the parent and the nested elements of that object were the ones that changed. The object remain the same. So with the shallow equality check that connect brings the component will never update. My solution was in the parent use Object.assign({}, dataObject) when I pass down the prop so I make another different object. Now shallow equality check could compare it and determinate that the props have changed and there before update the component.
i had same problem and i used object.assign for create new state but i use combineReducer and it cause multi level state ,in my case i pass whole state as props to component so shallow equality check can not detect my state change so componentDidUpdate didnot call,it is important to pass state in level it change when using combine reducer in my case i pass it like this const MapStateToProps=(state)=>{ return { reportConfig:state.ReportReducer } }; and my state tree is like this { ReportReducer: { reportConfig: { reportDateFilter: 'this week', reportType: null, reportShopId: null, updateShop: true } } } and in my reducer and return it like this as ReportReducer export default combineReducers({reportConfig}); and my root reducer is like this const rootReducer =combineReducers({ReportReducer}); const store = createStore(rootReducer ,{},enhancer);
Another option that you can use is to make a deep copy of the inherit prop this.props.dataObject on the child component, this in order for the componentDidUpdate to 'catch' the updated prop, you could use: dataObject={JSON.parse(JSON.stringify(valueToPass))} Use this where you are passing the prop from the parent component, this works for me in a similar problem (This applies when you don't have any function inside the prop).
I had this exact same problem with Components I used from an external library. So I didn't had the option to modify the inherited property. I only needed a part of the inherited property object (will use dataObject for simplicity). Solved it by adding it to the mapStateToProps function: function mapStateToProps(state, ownProps) { return { libraries: state.libraries, neededValue: ownProps.dataObject.value } } By which a shallow compare is enough to notice a value change. So use this.props.neededValue iso this.props.dataObject.value in the render() function.