177177177
177177177•12mo ago

Signal value not updating from inside of `for await of` loop inside of an IIFE

// routes/connect.tsx
import type { FunctionComponent } from 'preact'
import type { PageProps } from '$fresh/server.ts'

const api_version = 10

export default ((props) => {
const message = useSignal('Connecting')

;(async () => {
const wss = new WebSocketStream(`${
(await (
await fetch(`https://discord.com/api/v${api_version}/gateway`)
).json()).url
}/?v=${api_version}&encoding=json`)
const { readable } = await wss.opened

for await (const msg of readable) {
message.value = msg // not updating message.value

console.log(msg) // this works
}
})()

message.value = 'updated outside of the IIFE' // this works too

return (
<div class='max-w-screen-md mx-auto flex flex-col items-center justify-center'>
<div class='flex gap-8 py-6'>
<button>{message}</button>
</div>
</div>
)
}) satisfies <FunctionComponent<PageProps>>
// routes/connect.tsx
import type { FunctionComponent } from 'preact'
import type { PageProps } from '$fresh/server.ts'

const api_version = 10

export default ((props) => {
const message = useSignal('Connecting')

;(async () => {
const wss = new WebSocketStream(`${
(await (
await fetch(`https://discord.com/api/v${api_version}/gateway`)
).json()).url
}/?v=${api_version}&encoding=json`)
const { readable } = await wss.opened

for await (const msg of readable) {
message.value = msg // not updating message.value

console.log(msg) // this works
}
})()

message.value = 'updated outside of the IIFE' // this works too

return (
<div class='max-w-screen-md mx-auto flex flex-col items-center justify-center'>
<div class='flex gap-8 py-6'>
<button>{message}</button>
</div>
</div>
)
}) satisfies <FunctionComponent<PageProps>>
I'm using an IIFE here because otherwise the for await of loop will block the code and page keeps loading till WebSocketStream connection ends, so I did that. But why does setting message.value = msg not update the button text, it is stuck at the initial Connecting text. But when I set message.value = 'updated outside of the IIFE' then it works fine, the value is updating as expected. The console.log(msg) inside the IIFE's for await of loop also works and logs the msg to the console, but still message.value = msg doesn't. Why ??
10 Replies
Deno AI Helper
Deno AI Helper•12mo ago
I wasn't able to find the answer to your question in the official Deno documentation. However, based on the code snippet you've provided, there are a few things I can suggest that might help you troubleshoot the issue. Firstly, it seems like you are trying to use a reactive state management pattern with useSignal, which is not updating as expected inside the for await of loop. This pattern is commonly used in reactive frameworks like React or Vue, but you're using Preact (as indicated by the import), and the code seems to be written for a server-side context ($fresh/server.ts). It's important to ensure that the useSignal hook or function you're using is compatible with Preact and is designed to work in a server-side environment like Fresh. Secondly, updating the state asynchronously inside an IIFE might not trigger a re-render of your component, depending on how useSignal is implemented. In reactive frameworks, state updates usually need to be tied to the component lifecycle to trigger re-renders. Here are a few things to consider: 1. Ensure that useSignal is a part of the Fresh framework or a compatible library that works with server-side rendering. 2. Verify that useSignal is designed to handle asynchronous updates. If it's not, the component may not re-render when message.value is updated inside the loop. 3. Check if there's a need to manually trigger a re-render or update the component state after setting message.value inside the loop. Please note that this advice is not based on the Deno documentation, and I advise you to double-check the accuracy of the advice given. It's also a good idea to consult the documentation or community resources for the Fresh framework or the library providing useSignal for more specific guidance on how to
Deno AI Helper
Deno AI Helper•12mo ago
handle asynchronous state updates in your components.
177177177
177177177OP•12mo ago
How can I manually trigger a re-render?
Deno AI Helper
Deno AI Helper•12mo ago
In the context of Fresh (which is built on top of Preact), you don't typically manually trigger re-renders because Fresh handles the server-side rendering, and Preact handles the client-side rendering. The re-rendering in Preact is usually triggered by state changes using the useState hook or by setting properties on components. However, since you're using useSignal, which is not a standard hook in Preact or Fresh, it's not clear how it's supposed to trigger updates. If useSignal is part of a state management library you're using, you would need to refer to that library's documentation to understand how to trigger re-renders when the state changes. In standard Preact (without additional libraries), you would use the useState hook to create a stateful value and a function to update it, like this:
import { h } from 'preact';
import { useState, useEffect } from 'preact/hooks';

const MyComponent = () => {
const [message, setMessage] = useState('Connecting');

useEffect(() => {
// Your WebSocket logic here
// When you receive a message, you can update the state:
// setMessage(newMessage);
}, []); // The empty array means this effect runs once on component mount

return (
<div>
<button>{message}</button>
</div>
);
};
import { h } from 'preact';
import { useState, useEffect } from 'preact/hooks';

const MyComponent = () => {
const [message, setMessage] = useState('Connecting');

useEffect(() => {
// Your WebSocket logic here
// When you receive a message, you can update the state:
// setMessage(newMessage);
}, []); // The empty array means this effect runs once on component mount

return (
<div>
<button>{message}</button>
</div>
);
};
When you call setMessage, Preact will automatically trigger a re-render of the component with the new state. If you're using a different state management library or pattern, you'll need to consult the documentation for that library to understand how to correctly trigger updates in your Fresh application. Remember that server-side rendering frameworks like Fresh have specific ways of handling state that may differ from client-side only frameworks.
177177177
177177177OP•12mo ago
useSignals hook is from @preact/signals
Deno AI Helper
Deno AI Helper•12mo ago
The useSignal hook from @preact/signals is designed to work with Preact to provide a reactive state management system. According to the documentation, when you update the value of a signal, it should trigger a re-render of the component that uses the signal. Here's an example from the documentation on how to use useSignal:
import { useSignal } from "@preact/signals";

export default function Counter(props) {
const count = useSignal(props.start);
return (
<div>
<p>{count}</p>
<button onClick={() => count.value--}>-1</button>
<button onClick={() => count.value++}>+1</button>
</div>
);
}
import { useSignal } from "@preact/signals";

export default function Counter(props) {
const count = useSignal(props.start);
return (
<div>
<p>{count}</p>
<button onClick={() => count.value--}>-1</button>
<button onClick={() => count.value++}>+1</button>
</div>
);
}
In this example, clicking the buttons will update the count.value, which should trigger a re-render of the Counter component, updating the displayed count. If you're not seeing the expected re-render when message.value is updated within the for await of loop, it could be due to several reasons: 1. Component Scope: Ensure that the signal is being used within the scope of a Preact component that is part of the rendering lifecycle. If the signal is updated outside of a component that Preact is actively managing, it won't know to re-render. 2. Asynchronous Updates: If the signal is being updated in an asynchronous context, such as within a WebSocket event handler, make sure that the component is still mounted and that Preact is aware of the update. Normally, Preact should handle this, but there might be edge cases depending on how the WebSocketStream is set up. 3. Fresh Specifics: Since Fresh handles server-side rendering and hydration, ensure that the signal is being used in a way that is compatible with Fresh's rendering model. For example, if the signal is being updated server-side after the initial render, it
Deno AI Helper
Deno AI Helper•12mo ago
won't be reflected client-side without hydration. If you've confirmed that the signal is being used correctly and you're still not seeing updates, you might want to use the Preact DevTools to inspect the component tree and state. This can help you debug issues with your components and understand how Preact is rendering them. Just make sure you're using the latest version of Fresh and have the Preact DevTools extension installed in your browser. Remember that the DevTools feature is only available during development; it's not available in production to reduce the amount of JavaScript shipped to clients. If you continue to experience issues, you may want to consult the Fresh community or the maintainers of @preact/signals for more specific guidance. 🦕
177177177
177177177OP•12mo ago
I can't use useState hook because the code is in a route and not in an island.
Deno AI Helper
Deno AI Helper•12mo ago
You are correct that the useState hook is typically used within Preact components, and in the context of Fresh, state management with hooks like useState is generally used within islands, which are client-side interactive components. Since you are working within a route and not an island, the reactive state management provided by useSignal from @preact/signals should be the appropriate choice for managing state that updates in response to asynchronous events, such as messages from a WebSocket. The documentation provides examples of using useSignal within components, and it should work similarly within a route. When you update the value of a signal, it should trigger a re-render of the component that uses the signal, as long as it is within the scope of Preact's rendering lifecycle. If message.value is not updating as expected within the for await of loop, here are a few things you could check: 1. Component Updates: Ensure that the component which displays message is actually re-rendering. If the signal is updated but the component does not re-render, the new value will not be displayed. 2. Signal Usage: Double-check that you are using the signal correctly according to the documentation. The value should be accessed with message.value when reading and writing. 3. Error Handling: Make sure there are no errors in the WebSocket event handling that could be preventing the signal update from occurring. 4. Server-Side Considerations: Since you are in a route, consider whether the signal update is happening on the server side after the initial page load. If so, the update won't be reflected on the client side without additional client-side JavaScript to handle the reactivity. 5. Hydration: If the component needs to be interactive
Deno AI Helper
Deno AI Helper•12mo ago
after the initial server-side render, you might need to convert it into an island to ensure that it hydrates properly on the client side and responds to state changes. If after checking these points you still face issues, it might be necessary to look into more specific details of how useSignal is implemented or seek further assistance from the Fresh community or the maintainers of @preact/signals. Remember that debugging tools like Preact DevTools can be very helpful in these situations to inspect the component tree and state during development. 🦕