Streaming list updates
Streaming list updates¶
This example shows updates that no client asked for: a background timer
appends one number to rx[list[int]] every tick, entirely independent of
any client action. Each tick sends one small update — the message size
never grows with how many items the list already holds.
Backend¶
import asyncio
from rxdjango import ContextChannel, rx, action
class StreamingListChannel(ContextChannel):
"""Background-timer appends to `rx[list[int]]`: every tick sends one
small insert, however long the list grows, never a full re-send.
`reactive_model`'s background-work example (`ReactiveModelChannel`) uses
a real OS `threading.Thread` because its work is a *blocking* Django ORM
call. This channel's periodic work is a plain `asyncio.sleep` with no
blocking I/O, so the safe and idiomatic Channels equivalent is an
`asyncio.Task` cooperating on the consumer's own event loop instead of a
second OS thread — mutating `self.items` from a genuinely different
thread would race the consumer's own coroutine touching the same
websocket.
"""
TICK_SECONDS = 0.3
items = rx[list[int]]([])
ticking = rx[bool](True)
async def on_connect(self):
self._next_value = 1
self._task = asyncio.create_task(self._tick_loop())
async def on_disconnect(self):
task = getattr(self, '_task', None)
if task is not None:
task.cancel()
async def _tick_loop(self):
try:
while True:
await asyncio.sleep(self.TICK_SECONDS)
if not self.ticking:
continue
self.items.append(self._next_value)
self._next_value += 1
# Outside an action, nothing flushes the queued update —
# push it explicitly.
await self._consumer._flush_rx()
except asyncio.CancelledError:
pass
@action
async def pause(self):
self.ticking = False
@action
async def resume(self):
self.ticking = True
@action
async def reset(self):
self.items = []
Frontend¶
import React from 'react';
import { useChannel } from '@rxdjango/react';
import { StreamingListChannel } from '../../rx/streaming_list/streaming_list.channels';
import { Demo, Fields, Field, Button, Row, Note } from '../../components/demo';
export function StreamingListDemo() {
const channel = useChannel(StreamingListChannel);
return (
<Demo>
<Fields>
<Field label="Streamed items">
<ul className="flex flex-wrap gap-2">
{channel.items.map((item, index) => (
<li
key={index}
className="rounded-md border border-ink/40 px-2 py-1 text-sm tabular-nums"
>
{item}
</li>
))}
</ul>
</Field>
</Fields>
<Note>
A background timer appends one number every tick — no page reload,
no full re-send, just a small insert op per item.
</Note>
<Row>
{channel.ticking ? (
<Button variant="secondary" onClick={() => channel.pause()}>
Pause
</Button>
) : (
<Button onClick={() => channel.resume()}>
Resume
</Button>
)}
<Button variant="secondary" onClick={() => channel.reset()}>
Reset
</Button>
</Row>
</Demo>
);
}
export default StreamingListDemo;