[javascript] 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.

This question is related to javascript html reactjs

The answer is


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.


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]);

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.


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


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

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


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.


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

Use the setState function. So you could do

       this.setState({this.state.foo.bar:123}) 

inside the handle event method.

Once, the state is updated, it will trigger changes, and re-render will take place.


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

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

Examples related to javascript

need to add a class to an element How to make a variable accessible outside a function? Hide Signs that Meteor.js was Used How to create a showdown.js markdown extension Please help me convert this script to a simple image slider Highlight Anchor Links when user manually scrolls? Summing radio input values How to execute an action before close metro app WinJS javascript, for loop defines a dynamic variable name Getting all files in directory with ajax

Examples related to html

Embed ruby within URL : Middleman Blog Please help me convert this script to a simple image slider Generating a list of pages (not posts) without the index file Why there is this "clear" class before footer? Is it possible to change the content HTML5 alert messages? Getting all files in directory with ajax DevTools failed to load SourceMap: Could not load content for chrome-extension How to set width of mat-table column in angular? How to open a link in new tab using angular? ERROR Error: Uncaught (in promise), Cannot match any routes. URL Segment

Examples related to reactjs

Error: Node Sass version 5.0.0 is incompatible with ^4.0.0 TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received type undefined raised when starting react app Template not provided using create-react-app How to resolve the error on 'react-native start' Element implicitly has an 'any' type because expression of type 'string' can't be used to index Invalid hook call. Hooks can only be called inside of the body of a function component How to style components using makeStyles and still have lifecycle methods in Material UI? React Hook "useState" is called in function "app" which is neither a React function component or a custom React Hook function How to fix missing dependency warning when using useEffect React Hook? Unable to load script.Make sure you are either running a Metro server or that your bundle 'index.android.bundle' is packaged correctly for release