0

I'm new to StackOverflow and looking forward to contributing back to the community!

My first question, I am trying to make some squares change color on the screeen, after an onClick event. I'm nearly there, but I keep getting an error when I try to update the state, which then should updates the color. Please could you let me know what I'm doing wrong?

App.js

import React from "react"
import boxes from "./boxes"
import Box from "./Box"

export default function App() {
    const [squares, setSquares] = React.useState(boxes)
    function changeOn() {
        
       console.log(squares)//just checking I'm getting the full object
       setSquares({
        
            id: 1, on: false //this was previously [...prev], on: !squares.on
        })
        
    }

    const squaresElement = squares.map(props => (
    
        <Box key={props.id} on={props.on} onClick={changeOn} />
    ))

    return (
        <main>
        
            {squaresElement}
        </main>
    )
}

Box.js

import React from "react"

export default function Box (props) {
    const styles= props.on ? {backgroundColor: "#222222"} : {backgroundColor: "none"}

    return (
        
    
        <div className="box" style={styles} onClick={props.onClick}></div>
    )
}

Boxes.js

export default [
    {
        id: 1,
        on: true
    },   
    {
        id: 2,
        on: false
    },   
    {
        id: 3,
        on: true
    },   
    {
        id: 4,
        on: true
    },   
    {
        id: 5,
        on: false
    },   
    {
        id: 6,
        on: false
    },   
]

I hope somebody can easily spot what's wrong here?

I was expecting to see the color of the top left box change to a different color, after a click.

Texas12
  • 7
  • 3
  • Your comment *'//this was previously [...prev], on: !squares.on'* possibly points to you knowing what is wrong, which is that you need to maintain the array, but update one object. – pilchard Jan 31 '23 at 20:50
  • some duplicates: [How to update state with usestate in an array of objects?](https://stackoverflow.com/questions/62918710/how-to-update-state-with-usestate-in-an-array-of-objects) and [How to update a single key value pair in an array of objects in react?](https://stackoverflow.com/questions/71022511/how-to-update-a-single-key-value-pair-in-an-array-of-objects-in-react) – pilchard Jan 31 '23 at 20:52

2 Answers2

0

You're calling setSquares and passing it a single object instead of an array.

On the next render squares.map(...) blows up because squares is the object, and the object doesn't have a map method.

// after this call squares is just this one object
setSquares({
  id: 1, on: false
})

Here's a possible implementation that pushes the on/off responsibility into the box component itself.

// generates a list of items (faking your boxes.js)
const boxes = Array.from({length: 9}, (_, id) => ({ id }));

// container element to render the list
function Boxen ({ items }) {
  return (
    <div className="container">
      {items.map((item, idx) => (
        <Box item={item} key={idx} />    
      ))}
    </div>
  )
}

// component for a single box that can toggle its own on/off state
function Box ({item}) {
  const [active, setActive] = React.useState();
  return (
    <div onClick={() => setActive(!active)} className={active ? 'active' : ''}>{item.id}</div>
  )
}

ReactDOM.render(<Boxen items={boxes}/>, document.getElementById('root'));
.container {
  display: grid;
  grid-template-columns: repeat(3, 100px);
  grid-template-rows: repeat(3, 100px);
  gap: 1em;
}

.container > * {
  display: flex;
  justify-content: center;
  align-items: center;
  background: skyblue;
}

.container > .active {
  background: slateblue;
  color: white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>

<div id="root"></div>
ray
  • 26,557
  • 5
  • 28
  • 27
0

There are two issues:

  1. setSquares needs the whole array, so you need to give it a new squares array
  2. The styling back to None does not work always. better to give it the white color again

Please find the codesandbox

export default function App() {
  const [squares, setSquares] = React.useState(boxes);
  function changeOn(id) {
    setSquares(
      squares.map((square) => {
        return { ...square, on: id === square.id ? !square.on : square.on };
      })
    );
  }

  const squaresElement = squares.map((props) => (
    <Box key={props.id} on={props.on} onClick={() => changeOn(props.id)} />
  ));

  return <main>{squaresElement}</main>;
}

And in Box.js

  const styles = props.on
    ? { backgroundColor: "#222222" }
    : { backgroundColor: "#fff" };
Snake_py
  • 393
  • 2
  • 11