So I want to render x amount of circles. Every circle is rendered on a random position on the screen. Each one of them has a value parameter, which is the amount that will be added to the total point count displayed in the top left corner. After clicking a circle, it should add its value to the count and disappear. It does what it should, however on click, all the other circles get rerendered with a new random position, which they shouldn't. The initially set x and y position should be generated once for each circle and stay like that. How do I prevent that from happening? Here is my code:
import { useState } from "react";
import "./App.css";
function App() {
const [count, setCount] = useState(0);
let ctr = 0;
function getRandom(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min) min);
}
function renderCircle(xPos, yPos, color, value) {
ctr ;
const handleClick = (id) => () => {
setCount(count value);
const el = document.getElementById(id);
el.style.visibility = "hidden";
};
return (
<div>
{
<svg
className="circle"
id={ctr}
onClick={handleClick(ctr)}
style={{
left: `calc(${xPos}vw - 60px)`,
top: `calc(${yPos}vw - 60px)`,
position: "absolute",
}}
>
<circle cx={30} cy={30} r={30} fill={color} />
</svg>
}
</div>
);
}
function renderCircles(amount) {
const arr = [];
for (let i = 0; i < amount; i ) {
let circle = renderCircle(
getRandom(3, 53),
getRandom(3, 40),
Object.keys(Color)[
Math.floor(Math.random() * Object.keys(Color).length)
],
getRandom(1, 100)
);
arr.push(circle);
}
return arr;
}
return (
<div className="App">
<div className="App-game">{renderCircles(15)}</div>
<div className='App-currency' style={{color: "black", fontSize: 80}}>
{count}
</div>
</div>
);
}
export default App;
class Color {
static Green = new Color("green");
static Blue = new Color("blue");
static Yellow = new Color("yellow");
static Red = new Color("red");
static Pink = new Color("pink");
static Orange = new Color("orange");
constructor(name) {
this.name = name;
}
toString() {
return `Color.${this.name}`;
}
}
CodePudding user response:
You should hold the circles in a state variable
const [circles] = useState(renderCircles(15));
return (
<div className="App">
<div className="App-game">{circles}</div>
<div className="App-currency" style={{ color: 'black', fontSize: 80 }}>
{count}
</div>
</div>
);
You also have an issue with the size of the svg wrapping the circles - it's much bigger, so the click event can be fired without actually clicking on the circle. You need to set the display to none to prevent the circle from being clicked on again as well.
function renderCircle(xPos, yPos, color, value) {
const id = (ctr ).toString();
function handleClick() {
setCount((prev) => prev value);
const el = document.getElementById(id);
el.style.display = 'none';
}
return (
<svg
className="circle"
id={id}
onClick={handleClick}
style={{
left: `calc(${xPos}vw - 60px)`,
top: `calc(${yPos}vw - 60px)`,
width: 60,
height: 60,
position: 'absolute',
}}
>
<circle cx={'50%'} cy={'50%'} r={'50%'} fill={color} />
</svg>
);
}
Stackblitz: https://stackblitz.com/edit/react-ts-b6jddu?file=App.tsx
Notice when incrementing the count I also changed the setCount
parameter to a callback function. You should make a habit of doing that when the new value relies on the previous value. Otherwise you may run into bugs down the line.
For example:
const {useState} = React;
function App() {
// count is a local variable, which the value of the state gets copied onto here
const [count, setCount] = useState(0);
function incrementThreeTimesWrong() {
// count is 0 here
setCount(count 1); // sets to 0 1
setCount(count 1); // sets to 0 1
setCount(count 1); // sets to 0 1
// count is still 0 here - setCount does not mutate the local variable
// However setCount triggers a rerender, which will update the local variable to 1
}
function incrementThreeTimesRight() {
setCount(prev => prev 1); // sets to 0 1
setCount(prev => prev 1); // sets to 1 1
setCount(prev => prev 1); // sets to 2 1
}
return (
<div>
<p>{count}</p>
<button onClick={incrementThreeTimesWrong}>Increment three times the wrong way</button>
<br/>
<button onClick={incrementThreeTimesRight}>Increment three times the right way</button>
</div>);
}
ReactDOM.createRoot(
document.getElementById("root")
).render(
<App />
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>