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
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 thesetInterval
, 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 —to get the interval working, especially the dependency for that
useEffect
is an empty block, ensuring that it won’t get accidentally overwritten somehow.Issue
The issue here is that you are directly mutating the DOM. This is a huge React anti-pattern.
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:
updatedTime
stateupdateTime
function intouseEffect
to remove it as an external dependency.updateTime
once outside the interval to "seed" the time.updatedTime
state.