270

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.

Tuomas Toivonen
  • 21,690
  • 47
  • 129
  • 225
  • 12
    Is this your code? Seems like it's not a valid React code –  Aug 11 '16 at 09:45
  • I think props value should not change in container component instead it should be change in the parent component by setState and that state should be map to the containers props – Piyush Patel Oct 14 '17 at 05:06
  • Use spread operator like this – Sourav Singh Feb 15 '20 at 05:26
  • 2
    @AdrianWydmanski and the other 5 people who upvoted: https://en.wikipedia.org/wiki/Pseudocode – David Newcomb May 28 '20 at 01:38
  • @PiyushPatel props are updated when a component is re-rendered in-place as the example of pseudo-code shows. Another example of this is with something like using ``, a link on the same page will update the props without creating a new instance and running the usual lifecycle events. – David Newcomb May 28 '20 at 01:46
  • @Tuomas Toivonen, Did you found solution for your question. Can you please accept or suggest solution. – Mahi Aug 30 '21 at 16:24
  • try it using key property for your child element – Swanand Taware Jul 28 '22 at 06:21

18 Answers18

329

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>
  }
}
polina-c
  • 6,245
  • 5
  • 25
  • 36
  • 15
    Thanks for this. I was using redux store and couldn't get my component to re-render until I added a key. (I used the uuid library to generate random key, and it worked). – Maiya Aug 23 '19 at 23:29
  • Using hooks my child would not re-render when setting state on an existing array. My (probably bad idea) hack was to simultaneously set the state of a dummy prop and add that to the useEffect dependency array: `[props.notRenderingArr, props.dummy]`. If the length of the array always changes, you could set the useEffect dependency array to `[props.notRenderingArr.length]`. – hipnosis Oct 15 '19 at 00:30
  • 13
    this was what I needed too. I'm puzzled, when is a `key` required, vs just `setState`? I have some children that rely on parents state but they aren't triggering a re-render... – dcsan Oct 27 '19 at 02:46
  • Great answer. But why this behavior? – Rishav Apr 15 '20 at 17:18
  • 4
    I was using index as a key but it was not functioning properly. Now I am using uuid and it's perfect :) Thanks to @Maiya – Ali Rehman Jun 19 '20 at 15:08
  • 2
    @dcsan I think the reason is that react uses the key property under the hood to calculate whether items that are rendered dynamically via map have been moved, etc. – Maiya Jun 19 '20 at 16:45
  • @Rishav (see answer in comment above) – Maiya Jun 19 '20 at 16:45
  • 1
    just sending the key as props in the parent also did the re-render in my case – zyrup Sep 27 '20 at 18:46
  • i ran into this issue when i was reusing the same template screen for different data, and I was trying to update based on the initial route params. Thank you polina – Qrow Saki Feb 07 '21 at 22:16
  • Type boolean is not assignable to key...? – The Muffin Man Jun 06 '21 at 06:57
  • didn't work at all in a functional component, don't know why. – schlingel Jun 22 '21 at 15:34
  • 9
    But why? This is a hack. What do I do if the child has three or four things it takes in props? Stringify them into a ridiculous hack to work around react not working as advertised? – doug65536 Nov 04 '21 at 18:38
  • Key could be length for the array type props – mohit uprim May 17 '22 at 11:22
  • 2
    This answer may work but it lacks a proper explanation on why a component doesn't update when its props change, see below @François Richard for the correct explanation (a component only renders for two reasons: 1. initial render and 2. the component's or one of its ancestors' state has been updated. Also, this is rather a messy hack than an elegant solution. – j3141592653589793238 Nov 01 '22 at 17:37
143

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

ericArbour
  • 579
  • 8
  • 12
François Richard
  • 6,817
  • 10
  • 43
  • 78
  • 7
    Why is it that setState will cause re-render but forceUpdate not? Also little off topic, but how are the components updated when props are passed from redux and it's state is updated through action? – Tuomas Toivonen Aug 11 '16 at 14:20
  • 1
    props are not updated even if you update the component the props you passed are still there. Flux is another topic yes :) – François Richard Aug 11 '16 at 15:30
  • 4
    state != props you need to read more about that. You do not make updates with props – François Richard Aug 11 '16 at 15:31
  • you can see it directly in source code, it's simply not the same process – Webwoman Jan 26 '19 at 19:32
  • so what you said here has either has been changed or I didn't understand it correctly, I made a question for that by the way, https://stackoverflow.com/questions/58903921/what-could-be-the-case-that-parrent-re-render-but-its-child-wont – ILoveReactAndNode Nov 17 '19 at 18:44
  • 1
    This isn't correct: https://reactjs.org/docs/react-component.html If Child and Container are components, then a re-render would occur. – Chaz Nov 21 '19 at 20:47
119

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]);
petrichor
  • 1,329
  • 1
  • 8
  • 6
  • 8
    This is definetely the correct answer, not to mention the simplest – Guilherme Ferreira Mar 16 '19 at 06:29
  • 2
    I also am using this approach. – rawsly Jul 01 '19 at 06:46
  • 1
    @vancy-pants The `componentDidUpdate` in this case goes in the Child component. – Nick Friskel Sep 06 '19 at 07:43
  • 12
    You don't need and shouldn't transform props to state in a child component. Just use the props directly. In `FunctionComponents` it will automatically update child components if the props change. – oemera Nov 18 '19 at 20:22
  • 1
    This approach also works when you have props in child components derived from parent state – Chefk5 Jan 30 '20 at 12:27
  • Thank you - was the only solution that worked for me when trying to open a menu component from another component – naturaljoin Mar 14 '20 at 11:00
  • But why componentDidUpdate renders twice...? if I add console.log(this.state.value) at the end of the componentDidUpdate after if statement, I can see that gets rendered twice. – Rehan Arshad Oct 20 '20 at 22:30
  • The component will render twice, because changing props will cause render of component, and then when props change you are updating the state, this will cause one more render. @RehanArshad – AConsumer May 21 '21 at 16:28
  • 1
    @oemera that was exactly what I did. When props sent from parent to child were updated the child didn't change its state accordingly. I would like to know why state does not follow props if it is set based on them. – Alessandro Sassi Jun 21 '21 at 22:42
  • @AlessandroSassi Then something else is wrong. I need to see the exact code. – oemera Jun 22 '21 at 06:46
  • @AlessandroSassi Create a new question or create a [codesandbox](https://codesandbox.io/) example. Or in the best case: do both. – oemera Jun 23 '21 at 08:28
35

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.

dnmeid
  • 386
  • 3
  • 4
  • 1
    Every new-ish React dev should read this answer until they totally understand it. Said another way: use a spread on a child prop object if the parent is only changing one of the object properties and it needs to propagate to child. Better yet, make each of the object properties a separate parent state / child prop. – moodboom Feb 23 '23 at 16:57
30

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}
         />
Michael Nelles
  • 5,426
  • 8
  • 41
  • 57
22

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

tjr226
  • 605
  • 1
  • 5
  • 15
13

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?

Sujan Thakare
  • 882
  • 1
  • 10
  • 27
  • Doesn't this also mean that container can't modify the props it receives from redux store? – Tuomas Toivonen Aug 11 '16 at 12:10
  • 4
    Container doesn't receives the props directly from the store, they are supplied by reading a part of a redux state tree (confusing??).!! confusion is because we don't know about the magical function connect by react-redux. if you see the source code of connect method, basically it creates a higher order component which has a state with property storeState and is subscribed to the redux store. as storeState changes the whole re-rendering happens, and the modified props get supplied to the container by reading the changed state. hope this answers the question – Sujan Thakare Aug 14 '16 at 18:29
  • Good explanation, thinking about higher order component helps me understand what is happening – Tuomas Toivonen Aug 15 '16 at 05:28
  • Parent can't change children's props either. – doug65536 Nov 04 '21 at 18:43
  • Depends on what you are trying to achieve. Parent can't poke around child props to change it. parent needs to re-render itself to pass updated props to child. in that sense parent can change child's props. – Sujan Thakare Nov 08 '21 at 10:41
8

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.

Leniel Maccaferri
  • 100,159
  • 46
  • 371
  • 480
JamesYin
  • 732
  • 9
  • 19
6

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>
        );
    }
}
5

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.

jizanthapus
  • 121
  • 1
  • 9
4

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.

mike_s
  • 111
  • 4
  • 1
    Underrated solution for when using a spread operator doesn't work! Was facing this issue with a Prosemirror based WSIYWG editor, this fixed – Benjamin Bialy Aug 11 '22 at 08:00
1

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);
סטנלי גרונן
  • 2,917
  • 23
  • 46
  • 68
satyam soni
  • 259
  • 1
  • 9
1
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.

β.εηοιτ.βε
  • 33,893
  • 13
  • 69
  • 83
0

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);
JAY PATEL
  • 559
  • 4
  • 17
Divyanshu Rawat
  • 4,421
  • 2
  • 37
  • 53
0

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

Rajesh N
  • 6,198
  • 2
  • 47
  • 58
0

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.

WillKre
  • 6,280
  • 6
  • 32
  • 62
0

You can use componentWillReceiveProps:

componentWillReceiveProps({bar}) {
    this.setState({...this.state, bar})
}

Credit to Josh Lunsford

Paul Roub
  • 36,322
  • 27
  • 84
  • 93
0

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} />;
};
Lucas Simões
  • 589
  • 5
  • 10