React click specific element in setInterval loop

Viewed 47

I'm trying to click my element in setInterval loop, so it would be clicked every 10 second, but there's always error click is not a function or cannot read click null

I've tired with useRef and also did nothing.

here is my code:

  useEffect(() => {
    setInterval(function () {
      const handleChangeState = () => {
        console.log("Now");
        document.getElementById("dice").click();
      };
      handleChangeState();
    }, 10 * 1000);
  }, []);

  return (
    <>
      <Dice id="dice" rollingTime="3000" triggers={["click", "P"]} />
    </>
  );
};

1 Answers

It is often considered anti-pattern in React to query the DOM. You should instead use a React ref to gain access to the underlying DOMNode.

There are a couple ways to use a React ref to invoke a dice roll of the child component. FYI, rollingTime should probably be number type instead of a string if using in any setTimeout calls.

  1. Forward the React ref and attach to the button element and invoke the click handler.

    Example:

    const Dice = forwardRef(({ id, rollingTime }, ref) => {
      const timerRef = useRef();
    
      const [value, setValue] = useState();
      const [isRolling, setIsRolling] = useState();
    
      useEffect(() => {
        return () => clearTimeout(timerRef.current);
      }, []);
    
      const roll = () => {
        if (!isRolling) {
          setIsRolling(true);
          clearTimeout(timerRef.current);
          timerRef.current = setTimeout(() => {
            setValue(Math.floor(Math.random() * 6) + 1);
            setIsRolling(false);
          }, rollingTime);
        }
      };
    
      return (
        <>
          <h1>Dice</h1>
          <h2>Roll Value: {isRolling ? "Rolling..." : value}</h2>
          <button ref={ref} id={id} type="button" onClick={roll}>
            Roll the dice
          </button>
        </>
      );
    });
    

    ...

    export default function App() {
      const diceRef = useRef();
    
      useEffect(() => {
        const handleChangeState = () => {
          console.log("Clicking Dice");
          diceRef.current?.click();
        };
    
        setInterval(() => {
          handleChangeState();
        }, 10 * 1000);
      }, []);
    
      return (
        <div className="App">
          <Dice
            ref={diceRef}
            id="dice"
            rollingTime={3000}
            triggers={["click", "P"]}
          />
        </div>
      );
    }
    
  2. Forward the React ref and invoke the button's callback function directly via the useImperativeHandle hook.

    Example:

    const Dice = forwardRef(({ id, rollingTime }, ref) => {
      const timerRef = useRef();
    
      const [value, setValue] = useState();
      const [isRolling, setIsRolling] = useState();
    
      useEffect(() => {
        return () => clearTimeout(timerRef.current);
      }, []);
    
      const roll = () => {
        if (!isRolling) {
          setIsRolling(true);
          clearTimeout(timerRef.current);
          timerRef.current = setTimeout(() => {
            setValue(Math.floor(Math.random() * 6) + 1);
            setIsRolling(false);
          }, rollingTime);
        }
      };
    
      useImperativeHandle(ref, () => ({
        roll
      }));
    
      return (
        <>
          <h1>Dice 2</h1>
          <h2>Roll Value: {isRolling ? "Rolling..." : value}</h2>
          <button id={id} type="button" onClick={roll}>
            Roll the dice
          </button>
        </>
      );
    });
    

    ...

    export default function App() {
      const diceRef = useRef();
    
      useEffect(() => {
        const handleRollDice = () => {
          console.log("Roll dice");
          diceRef.current.roll();
        };
    
        setInterval(() => {
          handleRollDice();
        }, 10 * 1000);
      }, []);
    
      return (
        <div className="App">
          <Dice
            ref={diceRef}
            id="dice"
            rollingTime={3000}
            triggers={["click", "P"]}
          />
        </div>
      );
    }
    

Edit react-click-specific-element-in-setinterval-loop

Using react-dice-roll

If you examine the react-dice-roll source code you'll see that the Dice component forwards a React ref and uses the useImperativeHandle hook to expose out a rollDice function.

Dice Source

const Dice = forwardRef((props: TProps, ref: React.MutableRefObject<TDiceRef>) => {
    ...

    const handleDiceRoll = (value?: TValue) => {
        let diceAudio: HTMLAudioElement;
        if (sound) {
            diceAudio = new Audio(sound);
            diceAudio.play();
        }
        setRolling(true);
        setTimeout(() => {
            let rollValue = Math.floor((Math.random() * 6) + 1) as TValue;

            if (value) rollValue = value;
            if (cheatValue) rollValue = cheatValue;
            
            setRolling(false);
            setValue(rollValue);
            
            if (diceAudio) diceAudio.pause();
            if (!onRoll) return;
            onRoll(rollValue);
        }, rollingTime);
    };

    useImperativeHandle(ref, () => ({ rollDice: handleDiceRoll }));

    ...

    return (
      ...
    )
});

Your code then just needs to create a React ref and pass it to the Dice component, and instantiate the interval in a mounting useEffect hook.

Example:

function App() {
  const diceRef = useRef();

  useEffect(() => {
    const rollDice = () => {
      console.log("Rolling Dice");
      diceRef.current.rollDice(); // <-- call rollDice function
    };

    // instantiate interval
    setInterval(() => {
      rollDice();
    }, 10 * 1000);

    // immediately invoke so we don't wait 10 seconds for first roll
    rollDice();
  }, []);

  return (
    <div className="App">
      <Dice
        ref={diceRef}
        id="dice"
        rollingTime={3000}
        triggers={["click", "P"]}
      />
    </div>
  );
}

Edit react-click-specific-element-in-setinterval-loop (forked)

Related