I am using astro and preact. I want this card to render and update its counter every second. Its supposed to show how long you havent smoked. I got a starting Date stored in localstorage. When I try to do this through a js script it seems to work. But when I want to use the useEffect() hook the timer just gets stuck at 0.
index.astro
---
import 'bootstrap-icons/font/bootstrap-icons.css';
import BaseLayout from '../layouts/BaseLayout.astro';
import FunFact from '../components/FunFact.astro';
import Card from '../components/Card';
import SmokeFreeTimeCard from '../components/SmokeFreeTimeCard';
const pageTitle = "Übersicht";
---
<BaseLayout pageTitle={pageTitle}>
<Card cardTitle="Funfact" iconClass="fa-regular fa-face-laugh">
<FunFact />
</Card>
<SmokeFreeTimeCard cardTitle="Rauchfreie Zeit" iconClass="fa-solid fa-ban-smoking">
</SmokeFreeTimeCard>
<Card cardTitle="Finanzen" iconClass="fa-solid fa-money-bill">
Du hast bereits <b>X€</b> gespart!<br/>
Dein jähliches Ersparnis beläuft sich auf <b>XXXX€</b>. Das sind <b>XXX€</b> im Monat bzw. <b>XX€</b> am Tag!
</Card>
<Card cardTitle="Konsum" iconClass="fa-solid fa-wand-magic-sparkles">
Du hast bereits <b>XX,XX Zigaretten</b> widerstanden.<br/>
Auf ein Jahr gerechnet sind das <b>XX.XXX Stück</b>
</Card>
<script>
import "../scripts/index.js";
</script>
</BaseLayout>
Card.jsx
import 'bootstrap/dist/css/bootstrap.min.css';
import '@fortawesome/fontawesome-free/css/all.min.css';
const Card = ({ cardTitle, iconClass, children }) => (
<div class="card" style={{ marginBottom: '20px' }}>
<div class="card-header" style={{ color: '#ACEB98' }}>
<i class={iconClass}></i> <b>{cardTitle}</b>
</div>
<div class="card-body">
<p class="card-text">
{children} {/* Hier werden die eingefügten Texte gerendert */}
</p>
</div>
</div>
);
export default Card;
SmokeFreeTimeCard.jsx
import { useState, useEffect } from 'preact/hooks';
import Card from '../components/Card';
const SmokeFreeTimeCard = ({ cardTitle, iconClass }) => {
const [timeElapsed, setTimeElapsed] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
const dateOfReturn = localStorage.getItem('dateOfReturn');
console.log('Date of Return:', dateOfReturn); // Überprüfe das Datum im localStorage
if (dateOfReturn) {
const startDate = new Date(dateOfReturn);
const currentDate = new Date();
const elapsedMilliseconds = currentDate - startDate;
console.log('Elapsed Milliseconds:', elapsedMilliseconds); // Überprüfe die vergangene Zeit
setTimeElapsed(elapsedMilliseconds);
}
}, 1000);
return () => clearInterval(interval);
}, []);
const formatTime = (milliseconds) => {
const seconds = Math.floor(milliseconds / 1000);
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const remainingSeconds = seconds % 60;
return `${hours} Stunden ${minutes} Minuten ${remainingSeconds} Sekunden`;
};
return (
<Card cardTitle={cardTitle} iconClass={iconClass}>
{typeof timeElapsed === 'number' && (
<>Du bist <b>{formatTime(timeElapsed)}</b> rauchfrei! Weiter so!</>
)}
</Card>
);
};
export default SmokeFreeTimeCard;
So as far as I researched, I found that any browser-related APIs are not accessible in Astro. As Astro components run on the server, so we can’t access these browser-specific objects.
The localStorage
is also a part of window
which is indeed a browser API. Hence, it will not be accessible by Astro directly.
The solution to fix this is hydrating the React/Preact component whereever it is being used. Thus, we need a client directive saying when to load it.
For this, you just need to add client:load
to your component in index.astro:
<SmokeFreeTimeCard
cardTitle="Rauchfreie Zeit"
iconClass="fa-solid fa-ban-smoking"
client:load
/>
Another solution could be to use the old-classic script
as you mentioned as well, mentioned in Astro documentation as:
If the code is in an Astro component, move it to a tag outside of the frontmatter. This tells Astro to run this code on the client, where document and window are available.