withastro / astro

The web framework for content-driven websites. ⭐️ Star to support our work!
https://astro.build
Other
44.04k stars 2.29k forks source link

Preact component using nanostore/persistent displays initial value on page load #11401

Open tordeu opened 4 days ago

tordeu commented 4 days ago

Astro Info

Astro                    v4.11.3
Node                     v21.2.0
System                   macOS (arm64)
Package Manager          npm
Output                   static
Adapter                  none
Integrations             @astrojs/preact

If this issue only occurs in one browser, which browser is a problem?

No response

Describe the Bug

I created a preact component using a persistent data store. I added two instances of the component in an astro page. Data is synchronized between them as expected. But when going to a different page and then coming back, the initial value is displayed.

In the console, I can see that read() is retrieving the correct value, though. It just looks as if no UI update is triggered afterwards. The component works as expected when using the client:only directive, but not when using client:load or client:visible.

Component:

import { useStore } from '@nanostores/preact'
import { text } from '../store';

export default function StoreInput() {

    const $text = useStore(text);
    console.log("text = ", $text);

    function read() {
        console.log("read ", $text)
        return $text;
    }

    return (
        <input
            value={read()}
            onInput={(e) => {
                console.log("update")
                text.set((e.target as HTMLInputElement).value)
            }} />
    )

}

(console.log statements and read() function are merely added to log the values to the console.)

Store:

import { persistentAtom } from '@nanostores/persistent'

export const text = persistentAtom<string>('text', 'en')

Page:

---
import StoreInput from '../component/storeInput.tsx';
---

<html lang="en">
    <head>
        <meta charset="utf-8" />
        <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
        <meta name="viewport" content="width=device-width" />
        <meta name="generator" content={Astro.generator} />
        <title>Astro</title>
    </head>
    <body>
        <h1>Astro</h1>

        <StoreInput client:load />
        <StoreInput client:only />

        <a href="/other">other</a>

    </body>
</html>

What's the expected result?

I expected the component to display the correct value from localStorage when coming back to the page.

Link to Minimal Reproducible Example

https://stackblitz.com/edit/github-6qhjd8?file=src%2Fcomponent%2FstoreInput.tsx

Participation

Fryuni commented 3 days ago

I don't think this is the same as nanostores/persistent#49

client:load renders on the server, which has no persistency and is then hydrated on the client which adds the listeners and bindings.

I modified your reproduction with an alert that shows which value is being read on the client for each one of them: https://stackblitz.com/edit/github-6qhjd8-4sejou?file=src%2Fcomponent%2FstoreInput.tsx

Both components read the correct value on the client, but the client:load won't replace the elements rendered on the server until the state changes.

Fryuni commented 3 days ago

It is unclear to me whether this is intentional with the rationale that the first render on the client should have the same result as the server render and, therefore, can skip changing the DOM.

matthewp commented 3 days ago

This sounds very familiar to https://github.com/withastro/astro/issues/5937

Yeah, Preact expects the first render to match and doesn't update if it doesn't.

tordeu commented 3 days ago

Interesting, thanks @Fryuni and @matthewp.

While client:load renders the component on the server, I was expecting this to happen:

  1. the server-rendered component is displayed (with the initial value)
  2. the javascript of the component is executed, loading the current value from the persistent nanostore
  3. when the current value is retrieved, this triggers a DOM update and the correct value shown

But because of your comment, I switched from preact to react and it is working as expected.