javascripthtmlcssreactjstimer

Live soccer board with time


I am trying to call a sample API and build a live soccer score, but my time is not updating live without refresh. How to update the time without refresh?

Providing the stackblitz and code below. I looked at the console and there are no errors. I am able to call the sample API.

https://stackblitz.com/edit/react-9ypawr?file=src%2FApp.js,src%2Fstyle.css

import React, { useState, useEffect, useRef } from 'react';

const Scoreboard = () => {
  const [homeScore, setHomeScore] = useState(0);
  const [awayScore, setAwayScore] = useState(0);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch(
          'https://jsonplaceholder.typicode.com/posts'
        );
        const data = await response.json();
        setHomeScore(data[0].id);
        setAwayScore(data[1].id);
      } catch (error) {
        console.error(error);
      }
    };

    fetchData();
  }, []);

  const timeInterval = useRef(null);

  useEffect(() => {
    if (!timeInterval.current) {
      timeInterval.current = setInterval(() => {
        updateTime(); // Call the updateTime function every second
      }, 1000);
    }

    return () => {
      clearInterval(timeInterval.current); // Clear the interval when the component unmounts
    };
  }, []);

  const updateTime = () => {
    const currentTime = new Date().toLocaleTimeString();
    const hours = new Date().getHours();
    const minutes = new Date().getMinutes();
    const seconds = new Date().getSeconds();

    const formattedTime = `${hours}:${minutes
      .toString()
      .padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;

    document.getElementById(
      'home-time'
    ).textContent = `Last updated: ${formattedTime}`;
    document.getElementById(
      'away-time'
    ).textContent = `Last updated: ${formattedTime}`;
  };

  return (
    <div className="scoreboard">
      <div className="team">
        <img
          src="https://upload.wikimedia.org/wikipedia/en/thumb/f/f8/Barcelona_logo.svg/220px-Barcelona_logo.svg.png"
          alt="Barcelona logo"
        />
        <h1>Barcelona</h1>
        <h2>{homeScore}</h2>
        <p id="home-time">Last updated: 00:00:00</p>
      </div>

      <div className="team">
        <img
          src="https://upload.wikimedia.org/wikipedia/en/thumb/a/a2/Real_Madrid_CF.svg/220px-Real_Madrid_CF.svg.png"
          alt="Real Madrid logo"
        />
        <h1>Real Madrid</h1>
        <h2>{awayScore}</h2>
        <p id="away-time">Last updated: 00:00:00</p>
      </div>
    </div>
  );
};

export default Scoreboard;

Solution

  • Issue

    The issue here is that you are directly mutating the DOM. This is a huge React anti-pattern.

    const updateTime = () => {
      const currentTime = new Date().toLocaleTimeString();
      const hours = new Date().getHours();
      const minutes = new Date().getMinutes();
      const seconds = new Date().getSeconds();
    
      const formattedTime = `${hours}:${minutes
        .toString()
        .padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
    
      document.getElementById(
        'home-time'
      ).textContent = `Last updated: ${formattedTime}`; // <-- DOM mutation!
      document.getElementById(
        'away-time'
      ).textContent = `Last updated: ${formattedTime}`; // <-- DOM mutation!
    };
    

    React doesn't necessarily know to rerender the page so the UI gets out-of-sync.

    Solution

    I suggest adding a updatedTime state and rewrite the code to update the state and trigger a component rerender so the rendered DOM is kept "synchronized". Remember that in React, the rendered UI, e.g. what's rendered to the DOM, is a function of state and props.

    Example:

    import React, { useState, useEffect, useRef } from 'react';
    
    const Scoreboard = () => {
      const [homeScore, setHomeScore] = useState(0);
      const [awayScore, setAwayScore] = useState(0);
      const [updatedTime, setUpdatedTime] = useState("00:00:00");
    
      useEffect(() => {
        const fetchData = async () => {
          try {
            const response = await fetch(
              'https://jsonplaceholder.typicode.com/posts'
            );
            const data = await response.json();
            setHomeScore(data[0].id);
            setAwayScore(data[1].id);
          } catch (error) {
            console.error(error);
          }
        };
    
        fetchData();
      }, []);
    
      const timeInterval = useRef(null);
    
      useEffect(() => {
        const updateTime = () => {
          const hours = new Date().getHours();
          const minutes = new Date().getMinutes();
          const seconds = new Date().getSeconds();
      
          const formattedTime = `${hours}:${minutes
            .toString()
            .padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
      
          setUpdatedTime(formattedTime);
        };
    
        // Call once initially to "seed" state value
        updateTime();
        
        // Call the updateTime function every second
        timeInterval.current = setInterval(updateTime , 1000);
    
        return () => {
          // Clear the interval when the component unmounts
          clearInterval(timeInterval.current);
        };
      }, []);
    
      return (
        <div className="scoreboard">
          <div className="team">
            <img
              src="https://upload.wikimedia.org/wikipedia/en/thumb/f/f8/Barcelona_logo.svg/220px-Barcelona_logo.svg.png"
              alt="Barcelona logo"
            />
            <h1>Barcelona</h1>
            <h2>{homeScore}</h2>
            <p id="home-time">Last updated: {updatedTime}</p>
          </div>
    
          <div className="team">
            <img
              src="https://upload.wikimedia.org/wikipedia/en/thumb/a/a2/Real_Madrid_CF.svg/220px-Real_Madrid_CF.svg.png"
              alt="Real Madrid logo"
            />
            <h1>Real Madrid</h1>
            <h2>{awayScore}</h2>
            <p id="away-time">Last updated: {updatedTime}</p>
          </div>
        </div>
      );
    };
    
    export default Scoreboard;
    

    Edit react-live-soccer-board-with-time