0

I'm building a react-app that rolls a dice in a certain interval, shows all the latest rolls and lets the user try to guess the next throw (just to give a simple idea).

I'll try to give you an overview of the important parts of my code. First the main view: (and here is a working example https://codesandbox.io/s/spring-sound-ydcevo?file=/src/App.js)

import React, { useState, useRef } from "react";
import DiceComponent from "./dice-component";

function View() {
  const [dice, setDice] = useState(0);
  const diceRef = useRef(null);

  return (
    <div>
      <DiceComponent setDice={setDice} diceRef={diceRef} dice={dice} />
      current throw: {dice}
    </div>
  );
}
export default View;

and the dice component:

import Dice from "react-dice-roll";
import React, { useEffect } from "react";

function DiceComponent({ diceRef, setDice, dice }) {
  const roll = () => {
    getRoll();
    rollDice();
  };

  const getRoll = () => {
    let diceRoll = Math.floor(Math.random() * 5) + 1;
    setDice(diceRoll);
  };

  const rollDice = () => {
    if (diceRef && diceRef.current) {
      diceRef.current.style.pointerEvents = "auto";
      diceRef.current.children[0].click();
      diceRef.current.style.pointerEvents = "none";
    }
  };

  useEffect(() => {
    const interval = setInterval(() => {
      roll();
    }, 3500);
    return () => clearInterval(interval);
  }, [dice, diceRef]);

  return (
    <div>
      <div ref={diceRef} style={{ pointerEvents: "none" }}>
        <Dice size={100} cheatValue={dice} key={"dice"} />
      </div>
    </div>
  );
}
export default DiceComponent;

And to give a quick explanation about the dice. I fetch a roll number from my BE with the fetch as you can see. The dice is supposed to show this fetched number (the dice var) after I roll it. This is possible with the cheatValue={dice}, which lets the dice always roll to the given number of cheatValue. After I fetch and set my variables, I want to roll the dice. I achieve this with useRef and calling a click with it. Normally the dice would only roll if it gets clicked. But because the dice is not supposed to be clicked, but rather just spin on its own, I create a custom click event to trigger the roll.

Everything works fine so far, but I have one problem, that i can't seem to fix. In the main view I display the current dice for testing and it shows that my dice component always rolls the last dice number and not the current one. So it's always one behind, but I want it to always throw the actual current dice number that was fetched.

I tried to put the useEffect and all the fetch and roll method into my main view, but that didn't change anything. I use useEffect because I had the problem, that my fetch call was always called multiple times, which messed up my project. Thats why I need useEffect.

Any questions, or ideas on how I can achieve this? My project is a lot bigger than this part that I'm showing, so I hope I got the problem out. If something is not clear please tell me.

Drew Reese
  • 165,259
  • 14
  • 153
  • 181
civlon
  • 11
  • 5

1 Answers1

0

The reason this is happening is because when rollDice() is called, it fires the click event on the Dice component with the current value of dice, not the one that you just calculated and passed to setDice().

You can treat firing the click event as a side-effect by wrapping it in a useEffect(), which will run after the compoenent gets rendered with the new value of dice.

import Dice from "react-dice-roll";
import React, { useEffect } from "react";

function DiceComponent({ diceRef, setDice, dice }) {
  useEffect(() => {
    if (diceRef && diceRef.current) {
      diceRef.current.style.pointerEvents = "auto";
      diceRef.current.children[0].click();
      diceRef.current.style.pointerEvents = "none";
    }
  }, [dice, diceRef]);

  useEffect(() => {
    const interval = setInterval(() => {
      let diceRoll = Math.floor(Math.random() * 5) + 1;
      setDice(diceRoll);
    }, 3500);
    return () => clearInterval(interval);
  }, [setDice]);

  return (
    <div>
      <div ref={diceRef} style={{ pointerEvents: "none" }}>
        <Dice size={100} cheatValue={dice} key={"dice"} />
      </div>
    </div>
  );
}
export default DiceComponent;

You'll notice that I moved some things around. For example, having the useEffect of the interval depend only on setDice means that you'll have the same instance of the interval for the life of the component.

Also, it's worth keeping an eye out for the hooks lint rules, they are generally quite useful.

Tiberiu Matei
  • 21
  • 1
  • 4