Home > Mobile >  Updating State via onClick, in a Component
Updating State via onClick, in a Component

Time:02-01

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.

CodePudding user response:

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

CodePudding user response:

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>

  • Related