'State not updating on click in React
I need to update the player state on click (setPlayerState) but it's not being doing on time. I mean, it updates later on, so I get the first state only after the second click when the state has changed again. Does anyone know how to solve this?
const [player, setPlayerState] = useState({
name,
assertions: 0,
score: 0,
gravatarEmail,
});
function handleClick({ target }) {
const id = target.getAttribute('data-testid').includes('correct');
let points = 0;
if (id) {
const level = questions[index].difficulty;
if (level === 'hard') points = THREE;
else if (level === 'medium') points = TWO;
else points = ONE;
setPlayerState({
...player,
assertions: player.assertions + 1,
score: player.score + (TEN + (timer * points)),
});
}
}
Solution 1:[1]
Some points in your codes stand out
- You reference
indexinside of yourif(id) ...conditional butindexis not set. ONE,TWO,THREE, andTENare not set.- Question scoring logic is tangled with state update logic
Here is a minimal complete example that fixes these things and shows a better way to write onClick. Run the code example and click the question buttons to see how assertions and score updates respectively.
function App({ questions = [] }) {
const [player, setPlayer] = React.useState({
name: "Hanna",
assertions: 0,
score: 0,
})
function calcScore(difficulty) {
switch (difficulty) {
case "hard": return 3
case "medium": return 2
default: return 1
}
}
function onClick(question) { return event => {
if (event.target.getAttribute("data-test-id").includes("correct"))
setPlayer({
...player,
assertions: player.assertions + 1,
score: player.score + calcScore(question.difficulty)
})
}}
return <div>
{questions.map((q, key) =>
<button key={key} data-test-id="correct" onClick={onClick(q)}>
{q.ask}
</button>
)}
<pre>{JSON.stringify(player, null, 2)}</pre>
</div>
}
const questions = [
{ ask: "What is?", difficulty: "easy" },
{ ask: "How come?", difficulty: "medium" },
{ ask: "Seriously?", difficulty: "hard" }
]
ReactDOM.render(<App questions={questions} />, document.querySelector("#app"))
<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="app"></div>
Solution 2:[2]
Add a click state and useEffect to respond to it's change. It effectively forces two passes for the re-render.
Also try setPlayerState with previous value parameter i.e setPlayerState(player => which sometimes helps when updates are not noticed.
const [player, setPlayerState] = useState({
name,
assertions: 0,
score: 0,
gravatarEmail,
});
const [clickTarget, setClickTarget] = useState(null)
function handleClick({ target }) {
setClickTarget(target )
}
useEffect(() => {
if (!clickTarget) return
const id = clickTarget.getAttribute('data-testid').includes('correct');
let points = 0;
if (id) {
const level = questions[index].difficulty;
points = (level === 'hard') ? THREE :
(level === 'medium') ? TWO : ONE;
setPlayerState(player => ({
...player,
assertions: player.assertions + 1,
score: player.score + (TEN + (timer * points)),
}))
}
setClickTarget(null)
}, [clickTarget])
Solution 3:[3]
You can try to replace setState function called with the below code block to make sure state updated:
setPlayerState(s => ({
...s,
assertions: s.assertions + 1,
score: s.score + (TEN + (timer * points)),
}));
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | ??? |
| Solution 2 | |
| Solution 3 | Dharman |
