<script lang="ts">
import { onMount } from "svelte";
let theme = localStorage.getItem('theme') ?? 'light';
let flag = false;
onMount(()=>{
flag = true
})
$: if (flag) {
if ( theme === 'dark') {
document.documentElement.classList.add("dark");
} else {
document.documentElement.classList.remove("dark");
}
localStorage.setItem("theme", theme);
}
const handleClick = () => {
theme = (theme === "light" ? "dark" : "light");
};
</script>
<button on:click={handleClick}>{theme === "dark" ? "🌕" : "🌑"}</button>
the icon flickers when dark mode is enabled, in light mode this doesnt happen, I'm assuming this happens because its defaulting to lightmode when it initially renders, how can i fix this?
The flicker is caused by an unexpected side effect of MPA (Multi Page Application). In SPA (Single Page Application), routing happens on client side and only a single page is fetched on startup, so the state of things such as menu or theme stay consistent on client side.
Astro being an MPA would require a server page fetch when jumping from page to page. If the server does not know what the client set as theme (dark or light), it has to send a default one, then only when the <script>
tag executes on the client, that's when the persisted state on client will kick in.
It is possible to refer to this problem as server/client state synchronization
To make the problem more spicy, we have to keep in mind that multiple clients could be using the website and not just one.
For the solution purpose, if we think of the theme dark/light as being a counter 0/1
I still want to mention this again in case of restriction for Static Site, then the two next solutions do not work, then fall back on client side routing is required => SPA
next solutions apply for Server Side Rendering SSR
set by the client and read by the server upon request to ensure correct state is sent back singe page load
here is the main code snippet on server side
let counter = 0
const cookie = Astro.cookies.get("counter")
if(cookie?.value){
counter = cookie.value
}
here the functions how to set and get the cookie on client side with vanialla js
function get_counter(){
const entry = document.cookie.split(';').find(entry=>entry.replace(' ','').startsWith('counter='))
if(entry){
return parseInt(entry.split('=')[1])
}else{
return 0
}
}
function set_counter(counter){
document.cookie = `counter=${counter}`
console.log(`new counter value = ${counter}`)
}
Cookies are a much simpler solution, but in case this does not work for some reason (blocked, do not want notification,...) it is possible with storage and url parameters
setting the url parameter on client side and managing storage
...
window.history.replaceState(null, null, `?session_id=${session_id}`);
...
let session_id = sessionStorage.getItem("session_id")
...
sessionStorage.setItem("counter",counter)
and this is how the server manages the session id
let session_id = suid()
if(Astro.url.searchParams.has('session_id')){
session_id = Astro.url.searchParams.get('session_id')
console.log(`index.astro> retrieved session_id from url param '${session_id}'`)
}else{
console.log(`index.astro> assigned new session_id '${session_id}'`)
}
github : https://github.com/MicroWebStacks/astro-examples#13_client-cookie-counter
Cookies did not got though some vm service provides, here a working one on Gitpod
github : https://github.com/MicroWebStacks/astro-examples#14_client-storage-counter
This runs anywhere so here example on Stackblitz : https://stackblitz.com/github/MicroWebStacks/astro-examples/tree/main/14_client-storage-counter