Empresas
Empregos
  • Sobre nós
  • Soluções
    • Publicação de vagas
      Publique sua vaga e receba candidatos qualificados em 48h.
    • Avaliações de candidatos
      Mais de 500 testes técnicos e psicológicos, mais anti-fraude.
    • Headhunting
      Busca executiva personalizada do início ao fim.
    • Folha de Pagamento + EOR
      Dispersão de folha e EOR em mais de 15 países da LATAM.
  • Preços
  • Empregos

0

306
Visualizações
React, variable speed Clock with setTimeout

goal: create a Clock component which calls a callback method at regular intervals, but whose speed can be controlled.

Tricky part: do not reset the clock timer immediately when the speed changes, but at the next "tick" check the desired speed and if it has changes, reset the current interval and schedule a new one. This is needed to keep the clock ticket at a smooth pace when changing the speed.

I thought that passing a function getDelay that returns the delay (instead of the value of the delay itself) would make this work, but it doesn't.

If I let useEffect track the getDelay function it will reset when the delay changes. If it don't track getDelay the speed will not change while the clock is running.

import React, { useEffect, useRef } from "react";

type Callback = () => void;

function useInterval(tickCallback: Callback, getDelay: () => number, isPlaying: boolean) {
    const refDelay = useRef<number>(getDelay());

    useEffect(() => {
        let id: number;
        console.log(`run useEffects`);

        function tick() {
            const newDelay = getDelay();
            if (tickCallback) {
                console.log(`newDelay: ${newDelay}`);
                tickCallback();
                if (newDelay !== refDelay.current) {
                    // if delay has changed, clear and schedule new interval
                    console.log(`delay changed. was ${refDelay.current} now is ${newDelay}`)
                    refDelay.current = newDelay;
                    clear();
                    playAndSchedule(newDelay);
                }
            }
        }
        
        /** clear interval, if any */
        function clear() {
            if (id) {
                console.log(`clear ${id}`)
                clearInterval(id);
            }
        }

        /** schedule interval and return cleanup function */
        function playAndSchedule(delay: number) {
            if (isPlaying) {
                id = window.setInterval(tick, delay);
                console.log(`schedule delay id ${id}. ms ${delay}`)
                return clear
            }
        }
        return playAndSchedule(refDelay.current);
    },
        // with getDelay here the clock is reset as soon as the delay value changes
        [isPlaying, getDelay]);
}

type ClockProps = {
    /** true if playing */
    isPlaying: boolean;

    /** return the current notes per minute */
    getNpm: () => number;

    /** function to be executed every tick */
    callback: () => void;
}

export function Clock(props: ClockProps) {
    const { isPlaying, getNpm, callback } = props;

    useInterval(
        callback,
        () => {
            console.log(`compute delay for npm ${getNpm()}`);
            return 60_000 / getNpm();
        },
        isPlaying);

    return (<React.Fragment />);
}
about 4 years ago · Juan Pablo Isaza
1 Respostas
Responde à pergunta

0

you can use something like this:

import React, { useCallback, useEffect, useMemo, useRef } from 'react';

function useInterval(tickCallback: () => void, delay: number, isPlaying: boolean) {
  const timeout = useRef<any>(null);
  const savedDelay = useRef(delay);
  const savedTickCallback = useRef(tickCallback);

  useEffect(() => {
    savedDelay.current = delay;
  }, [delay])

  useEffect(() => {
    savedTickCallback.current = tickCallback;
  }, [tickCallback])

  const startTimeout = useCallback(() => {
    const delay = savedDelay.current;
    console.log('next delay', delay);
    timeout.current = setTimeout(() => {
      console.log('delay done', delay);
      savedTickCallback.current();
      startTimeout();
    }, savedDelay.current);
  }, []);

  useEffect(() => {
      if (isPlaying) {
        if (!timeout.current) {
          startTimeout();
        }
      } else {
        if (timeout.current) {
          clearTimeout(timeout.current);
        }
      }
    },
    [isPlaying, startTimeout],
  );
}

type ClockProps = {
  /** true if playing */
  isPlaying: boolean;

  /** return the current notes per minute */
  getNpm: () => number;

  /** function to be executed every tick */
  callback: () => void;
}

export const Clock: React.FC<ClockProps> = ({ isPlaying, getNpm, callback }) => {

  const delay = useMemo(() => {
    console.log(`compute delay for npm ${getNpm()}`);
    return 60_000 / getNpm();
  }, [getNpm]);

  useInterval(callback, delay, isPlaying);

  return null;
};

about 4 years ago · Juan Pablo Isaza Relatório
Responde à pergunta
Encontrar trabalhos remotos

Descubra a nova forma de encontrar um emprego!

melhores empregos
Principais categorias de trabalho
Empresas
Postar vaga Preços Comercial
Jurídico
Termos e Condições Política de privacidade
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Recomende algumas ofertas para mim
Preciso de ajuda