Jack Hales
Jack Hales

Reputation: 1664

State list change not causing re-render Next.js/React

I've got the following state:

const [places, setPlaces] = useState(false)
const [selectedPlaces, setSelectedPlaces] = useState([])

I asynchronously populate places by calling an API that returns an array of objects that looks something like:

[
    {name: "Shop #1", id: 1},
    {name: "Shop #2", id: 2}
]

My goal is to render these objects, and have their ID to be added/removed from the selectedPlaces state.

Render:

return (
  <div>
    <div>
      You have selected {selectedPlaces.length} total places
    </div>
    (places === false)
      ? <div>Loading...</div>
      : places.map(place => { // render places from the places state when loaded
          let [name, id] = [place.name, place.id]
          return <div onClick={() => {
            setSelectedPlaces(selected => {
              selected.push("dummy data to simplify")
              return selected
            })
          }}>{name}</div>
        })

  </div>
)

I've removed the key and added dummy data to make the movements simpler.

The problem arises when clicking on a div, the "You have selected ... total places" doesn't refresh until I force a re-render using fast refresh or through other methods (using browser/NextJS). Is this correct behaviour? It's as-if the state isn't being changed, but a console.log on the setSelectedPlaces displays fresh data symbolizing it is being changed.

I've tried:

Upvotes: 0

Views: 429

Answers (1)

adiga
adiga

Reputation: 35261

  • Add a {} wrapper for the ternary operator:

    {
        places === false 
         ? (...)
         : (....)
    }
    
  • push mutates the state. Use spread or concat in setSelectedPlaces

    setSelectedPlaces(selected => 
      selected.concat("abc")
    )
    
  • let [name, id] = [place.name, place.id] can be change to let { name, id } = place

Here's a snippet:

const { useState } = React;

function App() {
  const [places, setPlaces] = useState([
      { name: "Shop #1", id: 1 },
      { name: "Shop #2", id: 2 }
  ])
  const [selectedPlaces, setSelectedPlaces] = useState([])
  
  const onPlaceClick = id => setSelectedPlaces(state => state.concat(id))
  
  return (
    <div>
      <div> You have selected {selectedPlaces.length} total places </div>
      { (places === false)
        ? <div>Loading...</div>
        : places.map(({ id, name }) => 
            <div onClick={_ => onPlaceClick(id)}>{name}</div>
          )
      }
      <span>{selectedPlaces.join()}</span>
    </div>
  )
}

ReactDOM.render(
  <App />,
  document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>

Upvotes: 2

Related Questions