1

I have 6 cubes displayed on the page and there is a counter. When you click on a cube, its background changes to red and the counter increases by one, but the counter does not want to increase. I always have it 1. But if you remove the setcub([...cubikinapole]) line, then the counter starts working as it should, but now the background does not change. What to do?

import "./styles.css";
import {useState} from 'react'
export default function App() {
  let [cubikinapole, setcub] = useState([
    {id:1,title:1, img:"https://www.zonkpro.ru/zonk/assets/dice/mini/1.png", style: 'none'},
    {id:2,title:2, img:"https://www.zonkpro.ru/zonk/assets/dice/mini/2.png" , style: 'none'},
    {id:3,title:3, img:"https://www.zonkpro.ru/zonk/assets/dice/mini/3.png" , style: 'none'},
    {id:4,title:4, img:"https://www.zonkpro.ru/zonk/assets/dice/mini/4.png" , style: 'none'},
    {id:5,title:5, img:"https://www.zonkpro.ru/zonk/assets/dice/mini/5.png" , style: 'none'},
    {id:6,title:6, img:"https://www.zonkpro.ru/zonk/assets/dice/mini/6.png" , style: 'none'},
  ])
  let [count,se] = useState(0)
  function  a(el) {
    se(count++)
    el.style = 'active'
    setcub([...cubikinapole])
    console.log(count)
  }
  return (
    <div className="App">
                          {cubikinapole.length !== 0 ? 
                    cubikinapole.map((el)=>
                    <div id='cub' key={el.id} className={el.style} onClick={()=>a(el)}>
                      <img src={el.img} />
                    </div>
                    )
                    : console.log() }
    </div>
  );
}

CSS

.active{
  background-color: red;
}
DreamBold
  • 2,727
  • 1
  • 9
  • 24
Venom
  • 47
  • 5
  • `div id='cub'` is wrong, you should use unique `id`s in your html – DreamBold Dec 18 '22 at 12:42
  • Do the cubes share the same counter or are they meant to each have their own counter? – Andy Dec 18 '22 at 12:42
  • @Andy the same counter – Venom Dec 18 '22 at 12:45
  • https://codesandbox.io/s/amazing-haze-0zdb26?file=/src/App.js Have a look at this, please – DreamBold Dec 18 '22 at 12:46
  • 0 you're never supposed to change the value of a useState by the value itself, only by the setter - what you're doing where you're writing se(count++) is giving the old value of the count to se and then directly changing count by count++, so it looks like a race condition, you can fix it by just changing it to se(count+1) and your code will run as it should – Tzachi Elrom Dec 18 '22 at 12:58

3 Answers3

1

You can try this code. count++ increases its value after the setCount function is executed, so you should use count+1 to pass the increased value to the function. And you'd better use useEffect to monitor the count value.

import "./styles.css";
import { useState, useEffect } from "react";
export default function App() {
  let [cubikinapole, setCub] = useState([
    {
      id: 1,
      title: 1,
      img: "https://www.zonkpro.ru/zonk/assets/dice/mini/1.png",
      style: "none"
    },
    {
      id: 2,
      title: 2,
      img: "https://www.zonkpro.ru/zonk/assets/dice/mini/2.png",
      style: "none"
    },
    {
      id: 3,
      title: 3,
      img: "https://www.zonkpro.ru/zonk/assets/dice/mini/3.png",
      style: "none"
    },
    {
      id: 4,
      title: 4,
      img: "https://www.zonkpro.ru/zonk/assets/dice/mini/4.png",
      style: "none"
    },
    {
      id: 5,
      title: 5,
      img: "https://www.zonkpro.ru/zonk/assets/dice/mini/5.png",
      style: "none"
    },
    {
      id: 6,
      title: 6,
      img: "https://www.zonkpro.ru/zonk/assets/dice/mini/6.png",
      style: "none"
    }
  ]);

  let [count, setCount] = useState(0);

  useEffect(() => {
    console.log(count);
  }, [count]);

  function a(el) {
    setCount((prev) => prev + 1);
    el.style = "active";
    setCub([...cubikinapole]);
  }
  return (
    <div className="App">
      {cubikinapole.length !== 0
        ? cubikinapole.map((el) => (
            <div key={el.id} className={el.style} onClick={(e) => a(el)}>
              <img src={el.img} alt={el.title} />
            </div>
          ))
        : console.log()}
    </div>
  );
}

Live demo: https://codesandbox.io/s/amazing-haze-0zdb26?file=/src/App.js:0-1369

DreamBold
  • 2,727
  • 1
  • 9
  • 24
1

you're never supposed to change the value of a useState by the value itself, only by the setter - what you're doing where you're writing se(count++) is giving the old value of the count to se and then directly changing count by count++,

so it looks like a race condition, you can fix it by just changing it to se(count+1) and your code will run as it should

Tzachi Elrom
  • 385
  • 1
  • 2
  • 19
1

There are a few issues. Note: I've changed the names of some variable for clarity.

  1. You can't set your counter like that. Ideally you want to take the previous state, and then update and return that. So setCount(count++) becomes setCount(prev => prev += 1).

  2. ids should be unique. If you want to uniquely identify an element you can use a data attribute, and then pick up that value in the handler.

  3. With the id in hand in your handler you should find the object in state that where the object id matches the id of the element you clicked on, and then update that. To do that make a copy of the state first, update the object, and then set the state using the changed copy.

  4. It's not really clear whether it was the background of the cube you wanted to change, or the image itself so I've just left your code as-is. For the actual image to change would be slightly more difficult.

const { useState } = React;

// For clarity I'm passing in the image
// config to the component, and then setting the
// cube state with it
function Example({ config }) {

  // Initialise the states
  const [ cubes, setCubes ] = useState(config);
  const [ count, setCount ] = useState(0);

  // When an element is clicked take the
  // id from its data set. Make a copy of the state
  // and then find the index of the object that has a
  // matching id. Then update that object with the new
  // style, and set both the cube and count states
  function handleClick(e) {
    const { id } = e.currentTarget.dataset;
    const copy = [...cubes];
    const index = copy.findIndex(cube => cube.id === Number(id));
    if (index >= 0) copy[index].style = 'active';
    setCubes(copy);
    setCount(prev => prev += 1);
  }

  // I've added a count element here.
  // `map` over state adding the data attribute
  // to the div element.
  return (
    <div className="App">
      <div className="counter">Count: {count}</div>
      {config.map(obj => {
        const { id, style, img } = obj;
        return (
          <div
            key={id}
            data-id={id}
            className={style}
            onClick={handleClick}
          ><img src={img} />
          </div>
        );
      })}
    </div>
  );

}

const config=[{id:1,title:1,img:"https://www.zonkpro.ru/zonk/assets/dice/mini/1.png",style:"none"},{id:2,title:2,img:"https://www.zonkpro.ru/zonk/assets/dice/mini/2.png",style:"none"},{id:3,title:3,img:"https://www.zonkpro.ru/zonk/assets/dice/mini/3.png",style:"none"},{id:4,title:4,img:"https://www.zonkpro.ru/zonk/assets/dice/mini/4.png",style:"none"},{id:5,title:5,img:"https://www.zonkpro.ru/zonk/assets/dice/mini/5.png",style:"none"},{id:6,title:6,img:"https://www.zonkpro.ru/zonk/assets/dice/mini/6.png",style:"none"}];

ReactDOM.render(
  <Example config={config} />,
  document.getElementById('react')
);
.active { background-color: red; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Andy
  • 61,948
  • 13
  • 68
  • 95
  • `You can't set your counter like that. Ideally you want to take the previous state, and then update and return that.` Why doesn't `setCount(++count);` work?? – DreamBold Jan 03 '23 at 18:15
  • 1
    Because it's mutating the state directly and React can't er "react" to that. @DreamBold. See [this answer](https://stackoverflow.com/a/40309023/1377002) for a fuller explanation, and also the answer with the big green tick here. – Andy Jan 03 '23 at 18:28