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;
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.
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 into useEffect
to remove it as an external dependency.updateTime
once outside the interval to "seed" the time.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;