skip to Main Content

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;

2

Answers


  1. Just get rid of the if statement to check whether the interval is initialized and stored in the ref (timeInterval.current) then it should work.

    The reason is that in the strict mode for React, the component is mounted -> unmounted -> remounted to identify potential unsafe lifecycles. This will only happen in development mode.

    In this case, the timeInterval.current was cleaned up after it was initialized with the setInterval, so the interval stopped working after the initial rendering.

    You should still keep the cleanup logic as a best practice for using useEffect in general, and also get rid of the —

    if (!timeInterval.current) {
    

    to get the interval working, especially the dependency for that useEffect is an empty block, ensuring that it won’t get accidentally overwritten somehow.

    Login or Signup to reply.
  2. 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:

    • Create an updatedTime state
    • Move the updateTime function into useEffect to remove it as an external dependency.
    • Invoke updateTime once outside the interval to "seed" the time.
    • Remove the conditional check that the timer is null, it starts out that way.
    • Update the rendered UI to render the updatedTime state.
    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

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search