react-countdown

repository·master·Indexed 21 days ago

https://github.com/ndresx/react-countdown

A customizable countdown component and Hook for React supporting various rendering modes, millisecond precision, and stopwatch functionality. It provides a Countdown component and a useCountdown hook (requiring React 18+), featuring a control API for starting, pausing, and stopping timers, as well as lifecycle callbacks like onComplete and onTick.

Tokens
9K
Snippets
24
Records
37
Agent score
71%

What's inside react-countdown

  1. Import Countdown and useCountdown

    master

    The package supports both ES modules and CommonJS and is tree-shakeable. You can import the main Countdown component and the useCountdown Hook from the package root:

    import Countdown, { useCountdown } from 'react-countdown';

    For optimized bundle sizes, you can use dedicated entry points to import only what you need:

    import Countdown from 'react-countdown/component';
    import { useCountdown } from 'react-countdown/hook';
  2. Install react-countdown

    master

    You can install the react-countdown package using npm, pnpm, or yarn.

    Note on Versions:

    • The current stable release is v2.3.6.
    • v3 is currently in beta. To install the beta version, use npm install react-countdown@next.
    npm install react-countdown --save
    pnpm add react-countdown
    yarn add react-countdown
  3. How the CountdownJs engine works

    master

    The CountdownJs class is a framework-agnostic engine that manages the countdown logic, state, and timing. It is independent of React and can be used in any environment.

    Core Responsibilities:

    • State Management: Maintains the CountdownState (current timeDelta and status).
    • Timing: Uses setInterval (via intervalDelay) to trigger ticks.
    • Subscription: Provides a .subscribe(listener) method that returns an unsubscribe function, allowing external stores or frameworks to react to state changes.
    • Lifecycle: Handles initialization (init), updates (update), and cleanup (destroy).

    In a React environment, the useCountdown hook acts as the adapter that connects this engine to the React component lifecycle.

  4. Fix countdown reset on re-render

    master

    If your countdown resets every time the component re-renders, it is likely because the date prop is being passed as a new instance (e.g., new Date()) on every render.

    To fix this:

    1. Store the target date in component state or a useRef hook so it persists across renders.
    2. Alternatively, use the freezeProps prop to disable this behavior.
  5. Fix SSR hydration mismatch errors

    master

    When using Server-Side Rendering (SSR), you might see the error "Warning: Text content did not match...". This happens because the server's time and the client's time differ during hydration.

    Solutions:

    1. Client-only rendering: Only render the countdown after the component has mounted on the client.
    const [mounted, setMounted] = useState(false);
    useEffect(() => setMounted(true), []);
    
    if (!mounted) return null; // or a placeholder
    return <Countdown {...props} />;
    1. Suppress warning: Use suppressHydrationWarning on the output element.
    2. Manual start: Set autoStart={false} and call api.start() via useEffect once the client is ready.
    const [mounted, setMounted] = useState(false);
    useEffect(() => setMounted(true), []);
    if (!mounted) return null; // render nothing, or your own placeholder, until client-side
    return <Countdown {...props} />;
  6. Building a Stopwatch using Countdown

    master

    A stopwatch can be implemented by using the Countdown component with the overtime prop enabled and setting the date to the current time (Date.now()).

    Because the timer starts at 0, the time delta's total is negative and completed is true from the first tick. However, the formatted values in the render props remain positive, allowing them to function as a count-up timer. You can control the stopwatch using the api object (start(), pause(), stop()).

    import { createRoot } from 'react-dom/client';
    import Countdown from 'react-countdown';
    
    createRoot(document.getElementById('root')).render(
      <Countdown
        date={Date.now()}
        overtime
        daysInHours
        autoStart={false}
        renderer={({ formatted, api }) => (
          <div>
            <span>
              {formatted.hours}:{formatted.minutes}:{formatted.seconds}
            </span>
            <button onClick={api.start}>{api.isPaused() ? 'Resume' : 'Start'}</button>
            <button onClick={api.pause}>Pause</button>
          </div>
        )}
      />
    );
  7. Using the useCountdown Hook

    master

    If you prefer using React Hooks instead of the Countdown component, use the useCountdown hook. Pass the configuration object (containing the date) to the hook.

    Note: If you are passing a dynamic object, you should wrap it in useRef or set freezeProps to true to prevent unnecessary re-renders.

    import { useRef } from 'react';
    import { createRoot } from 'react-dom/client';
    import { useCountdown } from 'react-countdown';
    
    const MyComponent = () => {
      const props = useRef({ date: Date.now() + 5000 });
      const { hours, minutes, seconds, api } = useCountdown(props.current);
      return <span>{api.isCompleted() ? 'Completionist!' : `${hours}:${minutes}:${seconds}`} </span>;
    };
    
    createRoot(document.getElementById('root')).render(<MyComponent />);
    import { useRef } from 'react';
    import { createRoot } from 'react-dom/client';
    import { useCountdown } from 'react-countdown';
    
    // Function component
    const MyComponent = () => {
      const props = useRef({ date: Date.now() + 5000 });
      const { hours, minutes, seconds, api } = useCountdown(props.current);
      return <span>{api.isCompleted() ? 'Completionist!' : `${hours}:${minutes}:${seconds}`} </span>;
    };
    
    createRoot(document.getElementById('root')).render(<MyComponent />);
  8. Countdown in Milliseconds

    master

    To display a countdown with millisecond precision, you must configure the following props:

    • intervalDelay: Set to a value lower than 1000ms (e.g., 0).
    • precision: Set to a value between 1 and 3.
    • renderer: A custom renderer to display the total property.
    import { createRoot } from 'react-dom/client';
    import Countdown from 'react-countdown';
    
    createRoot(document.getElementById('root')).render(
      <Countdown
        date={Date.now() + 10000}
        intervalDelay={0}
        precision={3}
        renderer={(props) => <div>{props.total}</div>}
      />
    );
  9. Custom and Conditional Rendering with renderer and onComplete

    master

    You can customize the visual output of the countdown or signal completion using the renderer prop and the onComplete callback.

    The renderer prop accepts a function that receives an object containing time units (hours, minutes, seconds, etc.) and a completed boolean. This allows you to return different UI elements based on whether the countdown has finished.

    import { createRoot } from 'react-dom/client';
    import Countdown from 'react-countdown';
    
    const Completionist = () => <span>You are good to go!</span>;
    
    const renderer = ({ hours, minutes, seconds, completed }) => {
      return completed ? (
        <Completionist />
      ) : (
        <span>{hours}:{minutes}:{seconds}</span>
      );
    };
    
    createRoot(document.getElementById('root')).render(
      <Countdown date={Date.now() + 5000} renderer={renderer} />
    );
    import { createRoot } from 'react-dom/client';
    import Countdown from 'react-countdown';
    
    // Random component
    const Completionist = () => <span>You are good to go!</span>;
    
    // Renderer callback with completed condition
    const renderer = ({ hours, minutes, seconds, completed }) => {
      return completed ? (
        <Completionist />
      ) : (
        <span>
          {hours}:{minutes}:{seconds}
        </span>
      );
    };
    
    createRoot(document.getElementById('root')).render(
      <Countdown date={Date.now() + 5000} renderer={renderer} />
    );
  10. Basic Usage of the Countdown component

    master

    To set up a simple countdown, provide a date prop to the Countdown component. The date prop should be a timestamp representing when the countdown should end.

    import { createRoot } from 'react-dom/client';
    import Countdown from 'react-countdown';
    
    // Counts down from 10 seconds
    createRoot(document.getElementById('root')).render(<Countdown date={Date.now() + 10000} />);
    import { createRoot } from 'react-dom/client';
    import Countdown from 'react-countdown';
    
    createRoot(document.getElementById('root')).render(<Countdown date={Date.now() + 10000} />);
  11. Use Lifecycle Callbacks to respond to countdown events

    master

    The <Countdown /> component accepts several optional lifecycle callbacks as props. These allow you to trigger side effects at specific stages of the countdown's lifecycle. Most callbacks receive a time delta object as their first argument.

    • onMount: Triggered when the component mounts.
    • onStart: Triggered whenever the countdown starts (including the first run).
    • onPause: Triggered every time the countdown is paused.
    • onStop: Triggered every time the countdown is stopped.
    • onTick: Triggered at every interval (defined by intervalDelay). Note: This only fires when controlled={false}. It fires for the final tick where completed: true is passed in the delta object.
    • onComplete: Triggered when the countdown ends. Unlike onTick, this works even if controlled={true}. It receives the time delta object as the first argument and a boolean as the second argument (indicating if it transitioned into the completed state or completed immediately on start).
  12. Use the `useCountdown` hook

    master

    The useCountdown hook provides direct access to the countdown's state and API. It is suitable for developers who want full control over the rendering logic without using the <Countdown /> component.

    Note: Requires React 18 or higher.

    Unlike the <Countdown /> component, the useCountdown hook does not support the renderer prop because the hook itself provides the necessary data to build any custom UI.

    Usage

    const { total, api } = useCountdown(props);

    The hook returns an object containing the current time delta (total) and the countdown's api.