Business
Jobs
  • About Us
  • Solutions
    • Job Postings
      Post your job and receive qualified candidates in 48h.
    • Candidate Assessments
      500+ technical and psychological tests, plus anti-fraud.
    • Headhunting
      Tailor-made executive search from start to finish.
    • Payroll + EOR
      Payroll dispersal and EOR across 15+ LATAM countries.
  • Pricing
  • Jobs

0

305
Views
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 answers
Answer question

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 Report
Answer question
Find remote jobs

Discover the new way to find a job!

Top jobs
Top job categories
Business
Post vacancy Pricing Sales
Legal
Terms and conditions Privacy policy
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Show me some job opportunities
There's an error!