Union and optional list elements

Union and optional list elements

This example shows the rest of rx[list[S]]’s type surface: list[int | str] mixes element types in one array, and list[int] | None distinguishes the field being unset (null) from being set to an empty list ([]) — two genuinely different states, on the wire and in the generated types.

Backend

from rxdjango import ContextChannel, rx, action


class ListTypesChannel(ContextChannel):
    """Union element types and an optional list.

    `mixed` shows `list[int | str]` rendering both element types in one
    array. `optional_numbers` shows an optional list: `null` (not set)
    is distinct from `[]` (set, but empty).
    """

    mixed = rx[list[int | str]]([1, 'two', 3])
    optional_numbers = rx[list[int] | None]()

    @action
    async def add_number(self, value: int):
        self.mixed.append(value)

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

    @action
    async def clear_mixed(self):
        self.mixed.clear()

    @action
    async def set_numbers(self, values: list[int]):
        self.optional_numbers = values

    @action
    async def append_number(self, value: int):
        self.optional_numbers.append(value)

    @action
    async def clear_numbers(self):
        self.optional_numbers = []

    @action
    async def unset_numbers(self):
        self.optional_numbers = None

Frontend

import React from 'react';
import { useChannel } from '@rxdjango/react';
import { ListTypesChannel } from '../../rx/list_types/list_types.channels';
import { Demo, Fields, Field, Button, Row, Note } from '../../components/demo';

export function ListTypesDemo() {
  const channel = useChannel(ListTypesChannel);
  const isUnset = channel.optional_numbers === null;

  return (
    <Demo>
      <Fields>
        <Field label="Mixed list (int | str)">
          <ul className="space-y-1">
            {channel.mixed.map((item, index) => (
              <li key={index}>
                <span className="mr-2 text-xs uppercase tracking-wide text-primary-700">
                  {typeof item}
                </span>
                <span>
                  {String(item)}
                </span>
              </li>
            ))}
          </ul>
        </Field>
        <Field label="Optional numbers (list[int] | None)">
          {isUnset ? (
            <Note>
              null (not set)
            </Note>
          ) : (
            <span>
              {channel.optional_numbers!.length === 0
                ? 'empty list'
                : channel.optional_numbers!.join(', ')}
            </span>
          )}
        </Field>
      </Fields>
      <Row>
        <Button onClick={() => channel.add_number(Math.floor(Math.random() * 100))}>
          Add number
        </Button>
        <Button onClick={() => channel.add_text('word')}>
          Add text
        </Button>
        <Button
          variant="secondary"
          onClick={() => channel.clear_mixed()}
        >
          Clear mixed
        </Button>
      </Row>
      <Row>
        <Button onClick={() => channel.set_numbers([1, 2, 3])}>
          Set numbers
        </Button>
        <Button
          variant="secondary"
          onClick={() => channel.append_number(Math.floor(Math.random() * 100))}
          disabled={isUnset}
        >
          Append number
        </Button>
        <Button
          variant="secondary"
          onClick={() => channel.clear_numbers()}
        >
          Clear (empty list)
        </Button>
        <Button
          variant="secondary"
          onClick={() => channel.unset_numbers()}
        >
          Unset (null)
        </Button>
      </Row>
    </Demo>
  );
}

export default ListTypesDemo;