Bye Bye Magical Svelte 4 $:
Following my recent post
We have two reactive variables and Svelte 4 solves the updates automatically. We only needed to remember that the right way is by reassigning the variable.
In Svelte 5 we should think a little how to achieve the same result. The two variables we are using are not enough, we need one more, the helper one.
Prefered way is to use a $derived() rune.
<script>
let value = $state();
let helperArr = [];
let derivedArr = $derived.by(() => {
if (value) {
helperArr.push(value);
return helperArr;
}
});
function random () {
value = Math.floor(1 + Math.random() * 10)
}
</script>
<button onclick={random}>Generate Random Value</button>
<p>value: {value}</p>
<p>derivedArr: {derivedArr}</p>
Real Life Example
This is the example how I tried to migrate quite stright forward Svelte 4 page to Svelte 5. It took me a while to rethink the code. This page works as a posts search with a "Load More" functionality (adding results or pagging if user does not have JS):
Svelte 4
<script>
import Icon from '../components/Icon.svelte';
import { enhance } from '$app/forms';
import { tick } from 'svelte';
export let form;
export let searchingLang;
export let l;
let results = [];
let previousSearch = '';
let searchTerm;
let skip;
$: if (!!form && form?.thereIsMore) {
searchTerm = form.searchTerm;
skip = Number(form?.skip) + 20;
}
$: if (!!form?.searchResultFromAction) {
if (previousSearch == form.searchTerm && form.thereWasMore) {
results = [...results, ...form.searchResultFromAction];
} else {
results = [...form.searchResultFromAction];
previousSearch = form.searchTerm;
}
}
async function intoView(el) {
await tick();
if (el.attributes.index.nodeValue == skip - 20 && skip != undefined) {
el.scrollIntoView({ behavior: 'smooth' });
}
}
</script>
{#if results.length}
<ol>
{#each results as item, index}
<li use:intoView {index} aria-posinset={index}>
<!-- users without javascript have calculated order of results within paggination and css disables standard ol ul numbering -->
<!-- users with javascript have standard ol ul numbering and loading more feature -->
<noscript>{Number(index) + 1 + Number(form?.skip)}. </noscript>
<a href="/act/{searchingLang}/{item.id}/present/text">{item.title}</a>
</li>
{/each}
</ol>
{#if form?.thereIsMore}
<form
method="POST"
action="?/search&skip={skip}&thereWasMore={form?.thereIsMore}"
use:enhance
autocomplete="off"
>
<label>
<!-- Probably we do not need to bind the value as this is hidden input -->
<!-- <input name="searchTerm" type="hidden" bind:value={searchTerm} /> -->
<input name="searchTerm" type="hidden" value={searchTerm} />
</label>
<button aria-label="Button to load more search results" class="outline">
<Icon name="loadMore" />
</button>
</form>
{/if}
{:else if form?.searchResultFromAction.length == 0}
{l.noResultsFound}
{/if}
<style>
@media (scripting: none) {
/* users without javascript have calculated order of results within paggination and css disables standard ol ul numbering
users with javascript have standard ol ul numbering and loading more feature */
ol {
list-style-type: none;
}
}
</style>
Svelte 5
<script>
import Icon from '../components/Icon.svelte';
import { enhance } from '$app/forms';
import { tick } from 'svelte';
let { form, searchingLang, l } = $props();
let previousSearch = '';
let skip = $derived.by(() => {
if (!!form && form?.thereIsMore) {
return Number(form?.skip) + 20;
}
});
let helperResultsArr = [];
let results = $derived.by(() => {
if (!!form?.searchResultFromAction) {
if (previousSearch == form.searchTerm && form.thereWasMore) {
helperResultsArr.push(...form.searchResultFromAction);
return helperResultsArr;
} else {
helperResultsArr = [];
helperResultsArr.push(...form.searchResultFromAction);
previousSearch = form.searchTerm;
return helperResultsArr;
}
} else return [];
});
async function intoView(el) {
await tick();
if (el.attributes.index.nodeValue == skip - 20 && skip != undefined) {
el.scrollIntoView({ behavior: 'smooth' });
}
}
</script>
{#if results.length}
<ol>
{#each results as item, index}
<li use:intoView {index} aria-posinset={index}>
<!-- users without javascript have calculated order of results within paggination and css disables standard ol ul numbering -->
<!-- users with javascript have standard ol ul numbering and loading more feature -->
<noscript>{Number(index) + 1 + Number(form?.skip)}. </noscript>
<a href="/act/{searchingLang}/{item.id}/present/text">{item.title}</a>
</li>
{/each}
</ol>
{#if form?.thereIsMore}
<form
method="POST"
action="?/search&skip={skip}&thereWasMore={form?.thereIsMore}"
use:enhance
autocomplete="off"
>
<label>
<input name="searchTerm" type="hidden" value={form.searchTerm} />
</label>
<button aria-label="Button to load more search results" class="outline">
<Icon name="loadMore" />
</button>
</form>
{/if}
{:else if form?.searchResultFromAction.length == 0}
{l.noResultsFound}
{/if}
<style>
@media (scripting: none) {
/* users without javascript have calculated order of results within paggination and css disables standard ol ul numbering
users with javascript have standard ol ul numbering and loading more feature */
ol {
list-style-type: none;
}
}
</style>
That is all for now.
PS: Do not hesitate to let me know if you would do the migration in a different way.
SOCIAL SHARE CARD GENERATOR