Home > Net >  State not updating on click in React
State not updating on click in React

Time:02-20

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)),
      });
    }
  }

CodePudding user response:

Some points in your codes stand out

  • You reference index inside of your if(id) ... conditional but index is not set.
  • ONE, TWO, THREE, and TEN are 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>

CodePudding user response:

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])
  • Related