I display some content to the user and the user chooses to hide it. I record their decision in localStorage so that when they come back to the website their choice is persisted. I’m not interested in a solution that relies on encoding this information into the URL.
When the page loads again, I read from localStorage. I must do this in onMount
because the Svelte Component is rendered server-side and then hydrated. As a result, the component defaults to the server’s state and then changes to reflect knowledge gained by accessing localStorage only after the component has mounted.
Here is an example:
<script lang="ts">
import { onMount } from "svelte";
let isHidden = $state(false);
onMount(async () => {
isHidden = localStorage.getItem('isHidden') === "false";
});
function toggle() {
isHidden = !isHidden;
localStorage.setItem('isHidden', `${isHidden}`);
}
</script>
<button onclick={toggle}>
Toggle
</button>
{#if !isHidden}
<div>Content</div>
{/if}
This example results in Content flickering from visible to hidden if localStorage is set to true.
The way I worked around this issue is by following Svelte’s guidance for avoiding FOUC (Flash Of Un-styled Content) with dark mode: https://flowbite.com/docs/customize/dark-mode/#dark-mode-switcher
I put a script in <head>
which reads the localStorage value and conditionally sets a class on the document. I gave content a named class and used global CSS to control its visibility based on the document class.
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
<script>
if (localStorage.getItem('isHidden') === 'false') {
document.documentElement.classList.add('isHidden');
} else {
document.documentElement.classList.remove('isHidden')
}
</script>
</head>
.isHidden .content {
display: none;
}
<script lang="ts">
function toggle() {
const isHidden = document.documentElement.classList.toggle('isHidden');
localStorage.setItem('isHidden', `${isHidden}`);
}
</script>
<button onclick={toggle}>
Toggle
</button>
<div class="content">Content</div>
This solution works, but breaks encapsulation quite heavily.
I was wondering if there were solutions which respected encapsulation?