React component not re-rendering on state change

My scenario was a little different. And I think that many newbies like me would be stumped - so sharing here.

My state variable is an array of JSON objects being managed with useState as below:

const [toCompare, setToCompare] = useState([]);

However when update the toCompare with setToCompare as in the below function - the re-render won't fire. And moving it to a different component didn't work either. Only when some other event would fire re-render - did the updated list show up.

const addUniversityToCompare = async(chiptoadd) =>
  {
      var currentToCompare = toCompare;
      currentToCompare.push(chiptoadd);
      setToCompare(currentToCompare);
  }

This was the solution for me. Basically - assigning the array was copying the reference - and react wouldn't see that as a change - since the ref to the array isn't being changed - only content within it. So in the below code - just copied the array using slice - without any change - and assigned it back after mods. Works perfectly fine.

const addUniversityToCompare = async (chiptoadd) => {
    var currentToCompare = toCompare.slice();
    currentToCompare.push(chiptoadd);
    setToCompare(currentToCompare);
}

Hope it helps someone like me. Anybody, please let me know if you feel I am wrong - or there is some other approach.

Thanks in advance.


I'd like to add to this the enormously simple, but oh so easily made mistake of writing:

this.state.something = 'changed';

... and then not understanding why it's not rendering and Googling and coming on this page, only to realize that you should have written:

this.setState({something: 'changed'});

React only triggers a re-render if you use setState to update the state.


Another oh-so-easy mistake, which was the source of the problem for me: I’d written my own shouldComponentUpdate method, which didn’t check the new state change I’d added.


That's because the response from chrome.runtime.sendMessage is asynchronous; here's the order of operations:

var newDeals = [];

// (1) first chrome.runtime.sendMessage is called, and *registers a callback*
// so that when the data comes back *in the future*
// the function will be called
chrome.runtime.sendMessage({...}, function(deals) {
  // (3) sometime in the future, this function runs,
  // but it's too late
  newDeals = deals;
});

// (2) this is called immediately, `newDeals` is an empty array
this.setState({ deals: newDeals });

When you pause the script with the debugger, you're giving the extension time to call the callback; by the time you continue, the data has arrived and it appears to work.

To fix, you want to do the setState call after the data comes back from the Chrome extension:

var newDeals = [];

// (1) first chrome.runtime.sendMessage is called, and *registers a callback*
// so that when the data comes back *in the future*
// the function will be called
chrome.runtime.sendMessage({...}, function(deals) {
  // (2) sometime in the future, this function runs
  newDeals = deals;

  // (3) now you can call `setState` with the data
  this.setState({ deals: newDeals });
}.bind(this)); // Don't forget to bind(this) (or use an arrow function)

[Edit]

If this doesn't work for you, check out the other answers on this question, which explain other reasons your component might not be updating.