I have a svelte component named [symbol].svelte in which I want to initiate a connection to a streaming service to receive server-sent events. I’ve not found a way to do this successfully.
Since EventSource only runs in the browser, I initialized it in the onMount
function like so:
<script> export let quote; let sse = {}; onMount(async () => { sse = new EventSource(`https://myurl.com?symbol=${quote.symbol}`); sse.onmessage = (event) => { let response = JSON.parse(event.data); if(!response.length) return; quote = response[0]; } }); onDestroy(() => { if(sse.readyState && sse.readyState === 1) { sse.close(); } }) </script> <div>{quote.symbol}</div>
This works fine, except when I navigate to another route that uses the same component- since the component doesn’t unmount and remount, onMount()
doesn’t fire and thus doesn’t instantiate a new SSE request. I don’t know of any way to easily force the component to remount, which would be simplest (relevant github issue here)
Another try was using a reactive statement like so:
<script> export let quote; let sse = {}; $: { if(process.browser === true) { //again, this stuff won't run on the server if(sse.readyState && sse.readyState === 1) { sse.close(); } sse = new EventSource(`https://myurl.com?symbol=${quote.symbol}`); } } sse.onmessage = (event) => { let response = JSON.parse(event.data); quote = response[0]; console.log(quote); } </script> <div>{quote.symbol}</div>
When changing routes, the quote variable changed, thus triggering the reactive statement to kill the existing SSE and instantiate a new one. Exceptthe onmessage handler wouldn’t fire, probably because the onmessage handler gets attached before the eventsource object is created.
Last take was to try with the onmessage handler in the reactive statement like so:
<script> export let quote; let sse = {}; $: { if(process.browser === true) { //again, this stuff won't run on the server if(sse.readyState && sse.readyState === 1) { sse.close(); } sse = new EventSource(`https://myurl.com?symbol=${quote.symbol}`); sse.onmessage = (event) => { let response = JSON.parse(event.data); quote = response[0]; console.log(quote); } } } </script> <div>{quote.symbol}</div>
The problem here is that since quote
gets reassigned as a product of the onmessage
handler, the reactive statement keeps firing circularly.
At this point I’m at a loss, any input would be appreciated!
Advertisement
Answer
It sounds like you want to use {#key ...}
, which causes its contents to be torn down and recreated when the value changes, including components:
{#key quote} <!-- destroyed and recreated whenever `quote` changes --> <Quote {quote}/> {/key}
Docs here: https://svelte.dev/docs#key
Incidentally, using onDestroy
is unnecessary if it’s only used to clean up work that happens in onMount
:
onMount(() => { const sse = new EventSource(`https://myurl.com?symbol=${quote.symbol}`); sse.onmessage = (event) => { let response = JSON.parse(event.data); if(!response.length) return; quote = response[0]; } }; return () => { if(sse.readyState === 1) { sse.close(); } }); });
This is better because you don’t have the top-level sse
variable, and because the returned cleanup function only needs in the browser, you don’t need to have the placeholder ssr = {}
assignment or check for sse.readyState
.