1

I'm trying out React hooks, and I'm very confused about what I'm seeing. I'm getting a JS object as a payload from my endpoint, and this will display and render fine if I pass the raw result:

...
import React, { useEffect, useState } from 'react'

const Status = () => {
  const [data, setData] = useState({})

  useEffect(() => {
    console.log('fetching status')
    const start = new Date()
    getStatus()
    .then(result => {
      console.log(`status fetched in ${(new Date() - start)}`)
      for (const key of Object.keys(result)) {
        if (!data[key]) {
          data[key] = {}
        }
        data[key]['status'] = result[key]
      }
      console.log(data);
      setData(result)
    })
    .catch(err => console.error(err))
  }, [])
  return (
    <div>
      { console.log('rendering')}
      <h1>Status</h1>
      <span>{JSON.stringify(data)}</span>
    </div>
  )
}

export default Status

... and I get two "rendering" logs as expected.

But when I change it to actually use the processed data instead:

...
import React, { useEffect, useState } from 'react'

const Status = () => {
  const [data, setData] = useState({})

  useEffect(() => {
    console.log('fetching status')
    const start = new Date()
    getStatus()
    .then(result => {
      console.log(`status fetched in ${(new Date() - start)}`)
      for (const key of Object.keys(result)) {
        if (!data[key]) {
          data[key] = {}
        }
        data[key]['status'] = result[key]
      }
      console.log(data);
      setData(data) // <--- here
    })
    .catch(err => console.error(err))
  }, [])
  return (
    <div>
      { console.log('rendering')}
      <h1>Status</h1>
      <span>{JSON.stringify(data)}</span>
    </div>
  )
}

export default Status

I don't get that second render, and the information isn't rendered on the page. Data is there as I verified in the console.log statement.

1 Answer 1

3

The reason is that you pass the same object to setData which will not cause a re-render because it's the same object.

React is basically doing this currentState === newStateFromSetState which will return true because it's the same object reference.

You need to pass a new object to setData like this

setData({ ...data });

This will make sure the object that is passed to setData is a not the same object but it's better to just construct a new object than mutating the current one.

const updatedData = { ...data }

for (const key of Object.keys(result)) {
  if (!updatedData[key]) {
    updatedData[key] = {}
  }
  updatedData[key]['status'] = result[key]
}

console.log(data)
setData(updatedData)
Sign up to request clarification or add additional context in comments.

3 Comments

This must be different from the class version of state updating, because I've been able to call this.setState(prev => { //modify prev and return it } and it causes a render
I've never tried to mutate and return prev, I just tested it and it indeed works, I guess react is doing something internally when returning something from this.setState callback
regardless, this is probably the more correct way to do it

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.