Search code examples
reactjstimerreduxbrowser-tab

How to synchronize countdown timers in multiple browser tabs?


I am developing an app on React where countdown timers are the main component (at the same time there can be 10-20 timers on the page). From the server I get: how long the timer should go and how much is left in seconds. Then every second I recount how much is left. The source data is stored in redux, and calculated in the component local state.

These timers should show the same values for every user.

The problem is when I duplicate the tabs in the browser, the api request does not occur, respectively, the timers in a new tab are rolled back to the old state. Updating data every second in redux seems to me not to be the best option, but I don’t see others yet.


Solution

  • You said that the server sends you the remaining time in seconds. So you can calculate on the client side when the countdown should end in client time. You can store that in local storage. When a new tab is opened you can use that value to initialize your timer.

    It does not require the client time to be correct or in sync with the server time as all tabs share the same (possibly wrong) client time. You are only interested in the difference in seconds between the current client time and the client time you saved to correctly initialize your timer.

    A solution to calculate it could roughly look like this:

    // when receiving the remaining seconds in the first tab
    
    const onReceivedRemaining = (remaining) => {
        const now = new Date(); // current client time
        now.setSeconds(now.getSeconds() + remaining); // timer end in client time
    
        localStorage.set('elapsing', JSON.stringify(now));
    }
    
    // when initializing the timer in a second tab
    
    const getInitial = () => {
        const elapsing_string = localStorage.get('elapsing');
        if (!elapsing_string) return null;
    
        const now = new Date();
        const elapsing = Date.parse(elapsing_string);
        return (elapsing - now) / 1000; // remaining time in seconds
    }