typescriptdenofreshjs

deno signal not updating in components


My Component

import { h } from 'preact';
import { quoteSignal } from '/quoteStore.ts';

export default function Button() {
  return (
    <button>hello 1 {quoteSignal.value}</button>
  );
}

my signal

import { signal } from '@preact/signals';

// Create a signal for the shared label
export const quoteSignal = signal('first');

My island

import { useEffect } from 'preact/hooks';
import { quoteSignal } from '/quoteStore.ts';

export default function QuoteIsland() {
  useEffect(() => {
    quoteSignal.value = "Test Label";
    console.log("useeffect")
    const fetchQuote = async () => {
      const response = await fetch('/api/quote/mystock');
      const data = await response.json();     
      console.log(data.price);
      quoteSignal.value = data.price;
    };

    fetchQuote();
    const intervalId = setInterval(fetchQuote, 10000); // 10 seconds

    return () => clearInterval(intervalId);
  }, []);

  return null; // This component does not need to render anything
}

Test Label is never displayed, as are none of the updates from api, even though the console log shows them. The button only ever displays as its label "first".


Solution

  • Fresh maintainer here.

    To share a signal across or with islands, pass them via props. Sharing data with islands in other ways like globals is not supported in Fresh.

    The reason this doesn't work is that Fresh only runs components on the server by default. When you create an island, we generate a JavaScript bundle that's optimized for the browser. During the bundling process that island is then inlined into the bundle. We then render the HTML on the server, send it to the browser and the browser instantiates the islands by loading the relevant bundles.

    The browser has no clue that you used a signal in the Button component since that was only ever rendered on the server. If Fresh would be a server-only framework that never ran JavaScript in the browser, sharing data via global signals would work.

    But the key strength of Fresh is its island architecture which allows you to run JavaScript in the browser for parts of the page that need it. If the Button component is only ever rendered on the server, then it will never be updated in the browser, regardless of how you passed data to it, like whether it came from a signal or somewhere else. The browser only sees the final HTML rendered by the server and then makes the islands interactive. It has no clue how that HTML was generated or if the resulting HTML of a Button component had a signal in it.

    The boundary as to how data is shared from the server with the browser is through props of an island. We serialize all props and send it along with the HTML to the browser. Anything not passed via props of an island will not be shared. And in the browser, the only components that are interactive are islands and components referenced inside of it. Nothing else will be interactive.