Reactive list of scalars

Reactive list of scalars

This example demonstrates rx[list[str]]: a reactive list field mutated in place with ordinary Python list methods. Every append, insert, __setitem__, del, pop and reassignment sends a small positional delta to the client instead of re-sending the whole list, however many items it holds.

Backend

from rxdjango import ContextChannel, rx, action


class ScalarListChannel(ContextChannel):
    """CRUD over a plain `rx[list[str]]` field.

    Every action below is a single Python list mutator; the field
    intercepts it and pushes a small delta to the client — no full-list
    re-send, however many items are in the list.
    """

    items = rx[list[str]](['apple', 'banana', 'cherry'])

    @action
    async def append(self, value: str):
        self.items.append(value)

    @action
    async def insert(self, index: int, value: str):
        self.items.insert(index, value)

    @action
    async def set_at(self, index: int, value: str):
        self.items[index] = value

    @action
    async def remove_at(self, index: int):
        del self.items[index]

    @action
    async def pop(self):
        return self.items.pop()

    @action
    async def replace_all(self):
        self.items = ['reset', 'from', 'scratch']

Frontend

import React, { useState } from 'react';
import { useChannel } from '@rxdjango/react';
import { ScalarListChannel } from '../../rx/scalar_list/scalar_list.channels';
import { Demo, Fields, Field, Button, TextInput, Row } from '../../components/demo';

export function ScalarListDemo() {
  const channel = useChannel(ScalarListChannel);
  const [draft, setDraft] = useState('');
  const [setIndex, setSetIndex] = useState('0');
  const [setValue, setSetValue] = useState('');

  const appendDraft = () => {
    if (!draft) return;
    channel.append(draft);
    setDraft('');
  };

  return (
    <Demo>
      <Fields>
        <Field label="Items">
          <ul className="space-y-2">
            {channel.items.map((item, index) => (
              <li
                key={`${index}-${item}`}
                className="flex items-center justify-between gap-3"
              >
                <span>
                  {item}
                </span>
                <Button
                  variant="secondary"
                  onClick={() => channel.remove_at(index)}
                >
                  Remove
                </Button>
              </li>
            ))}
          </ul>
        </Field>
      </Fields>
      <Row>
        <TextInput
          id="scalar-list-draft"
          label="New item"
          value={draft}
          onChange={setDraft}
        />
        <Button onClick={appendDraft}>
          Append
        </Button>
      </Row>
      <Row>
        <Button onClick={() => channel.insert(0, 'first')}>
          Insert at start
        </Button>
        <Button onClick={() => channel.pop()}>
          Pop last
        </Button>
        <Button variant="secondary" onClick={() => channel.replace_all()}>
          Replace all
        </Button>
      </Row>
      <Row>
        <TextInput
          id="scalar-list-set-index"
          label="Set index"
          value={setIndex}
          onChange={setSetIndex}
        />
        <TextInput
          id="scalar-list-set-value"
          label="Set value"
          value={setValue}
          onChange={setSetValue}
        />
        <Button
          variant="secondary"
          onClick={() => channel.set_at(Number(setIndex), setValue)}
        >
          Set
        </Button>
      </Row>
    </Demo>
  );
}

export default ScalarListDemo;