Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React: why child component doesn't update when prop changes

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.

like image 925
Tuomas Toivonen Avatar asked Aug 11 '16 09:08

Tuomas Toivonen


People also ask

Will child component Rerender if props change?

By default, when your component's state or props change, your component will re-render. If your render() method depends on some other data, you can tell React that the component needs re-rendering by calling forceUpdate() .

Does props change component update?

React components automatically re-render whenever there is a change in their state or props. A simple update of the state, from anywhere in the code, causes all the User Interface (UI) elements to be re-rendered automatically.

How do you force update a child component in React?

Forcing an update on a React class component In any user or system event, you can call the method this. forceUpdate() , which will cause render() to be called on the component, skipping shouldComponentUpdate() , and thus, forcing React to re-evaluate the Virtual DOM and DOM state.

Does child component Rerender if parent state changes?

State changes in Child component doesn't effect on the parent component, but when a state of parent component changes all the child components render.


13 Answers

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>
  }
}
like image 170
polina-c Avatar answered Oct 19 '22 04:10

polina-c


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

like image 32
François Richard Avatar answered Oct 19 '22 06:10

François Richard


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]);
like image 29
petrichor Avatar answered Oct 19 '22 06:10

petrichor


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}
         />
like image 29
Michael Nelles Avatar answered Oct 19 '22 05:10

Michael Nelles


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

like image 30
tjr226 Avatar answered Oct 19 '22 04:10

tjr226


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?

like image 40
Sujan Thakare Avatar answered Oct 19 '22 05:10

Sujan Thakare


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.

like image 32
dnmeid Avatar answered Oct 19 '22 05:10

dnmeid


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.

like image 29
JamesYin Avatar answered Oct 19 '22 04:10

JamesYin


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>
        );
    }
}
like image 29
Divyanshu Semwal Avatar answered Oct 19 '22 06:10

Divyanshu Semwal


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.

like image 44
jizanthapus Avatar answered Oct 19 '22 04:10

jizanthapus


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);
like image 33
satyam soni Avatar answered Oct 19 '22 06:10

satyam soni


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.

like image 33
ibrahim alsimiry Avatar answered Oct 19 '22 06:10

ibrahim alsimiry


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.

like image 40
mike_s Avatar answered Oct 19 '22 04:10

mike_s