opral / inlang-paraglide-js

Tree-shakable i18n library build on the inlang ecosystem.
https://inlang.com/m/gerre34r/library-inlang-paraglideJs
42 stars 0 forks source link

Svelte 5 $effect running multiple times upon language switch #43

Open yookers opened 6 months ago

yookers commented 6 months ago

On the latest version of Svelte 5 svelte@5.0.0-next.82, changing the language via a language switcher seems to trigger $effect multiple times, with each language switch increasing the amount of times $effect is run.

Here's the code that triggers the $effect multiple times, look at the amount of times running effect shows up in the console when you continuously alternate between clicking the en and fr button.

</x-turndown>
  //+page.svelte
  import { page } from "$app/stores";
  import { i18n } from "$lib/i18n";
  import { goto } from "$app/navigation";
  import {
    availableLanguageTags,
    type AvailableLanguageTag,
  } from "$paraglide/runtime";

  let { data } = $props();

  // Set the language cookie and reload the page with the new language.
  function set_language(lang: AvailableLanguageTag) {
    const canonical_path = i18n.route($page.url.pathname);
    // Append the search query to the resolved path.
    goto(i18n.resolveRoute(canonical_path + $page.url.search, lang), {
      noScroll: true,
      replaceState: true,
    });
  }

  let test_data = $state(data.load);

  $effect(() => {
    test_data = data.load;
    console.log("running effect");
  });

{#each availableLanguageTags as lang}

    {lang}

{/each}

{test_data}

[go to home](/)
//+page.server.ts
import type { PageServerLoad } from "./$types";

export const load = (async ({ depends }) => {
  depends("paraglide:lang");

  function getRandomInt(max: number) {
    return Math.floor(Math.random() * max);
  }

  return { load: getRandomInt(100) };
}) satisfies PageServerLoad;

On the previous Svelte 5 version svelte@5.0.0-next.81, $effect runs only 3 times each time the language switches. But on the latest version, the number of times $effect runs increases more and more, leading to very laggy behavior if $effect contains some complex operation every time the data changes from the server. Also going to a new page doesn't seem to reset the amount of times $effect is ran when returning back again. If you try going to a different page, like / and then returning back, $effect will still run many times.

yookers commented 6 months ago

As a temp fix, I made the language switcher use <a> tags and used data-sveltekit-reload to force refresh the page upon switching languages. This seems to not make the $effect run multiple times, even when going to a different page and returning back.

<script lang="ts">
  import { page } from "$app/stores";
  import { i18n } from "$lib/i18n";
  import {
    availableLanguageTags,
  } from "$paraglide/runtime";

  let { data } = $props();

  let test_data = $state(data.load);

  $effect(() => {
    test_data = data.load;
    console.log("running effect");
  });
</script>

{#each availableLanguageTags as lang}
  <div>
    <a
      href="{i18n.route($page.url.pathname)}"
      hreflang="{lang}"
      data-sveltekit-reload>
      {lang}
    </a>
  </div>
{/each}
LorisSigrist commented 6 months ago

Thanks for the reproduction! I can partially reproduce this. The $effect does run twice, but it only ever runs twice. There isn't this stacking behavior that you described, where $effect gets triggered an additional time for each language switch. I'll investigate why that happens

LorisSigrist commented 6 months ago

I'm pretty sure I know what's causing this. The <ParaglideJS> component triggers a re-render whenever the language changes. This detection is based on the URL. Paraglide detects the language switch _faster_ than the load function re-runs, so two re-renders are triggered. The first is for the language-change (reruns the $effect even if it isn't used, since it's re-mounted) and the second is for the load function.

This double-trigger doesn't appear to exist in Svelte 4 as the URL only updates once the load has run, so both updates are batched.

I'll need to think of a workaround here

LorisSigrist commented 6 months ago

Never-mind, I just re-tested, and the double-trigger does happen in Svelte 4, but in a different order. This seems to be an inherent side-effect of how the language state is managed at the moment (in the URL rather than page-data).

We'll likely need to stop detecting the language in the ParaglideJS component & instead pass it in from a load function. This will require a bit of API redesign & probably some breaking changes. For now, it's probably best to just document this quirk.

LargatSeif commented 4 months ago

Having same problem using svelte 5