Reactive model

Reactive model

Demonstrates that model changes made outside the channel context — such as in a background thread or a separate process — are pushed reactively to connected clients. A modify_project action spawns a thread that sleeps for a configurable delay, then fetches the Project from the database and saves a new name. The frontend receives the updated Task (with its nested Project) automatically, without any manual refresh.

Backend

import threading
import time

from rxdjango import ContextChannel, rx, action
from .models import Project, Task
from .serializers import TaskSerializer


class ReactiveModelChannel(ContextChannel):

    task = rx.model(TaskSerializer())

    async def on_connect(self):
        self.task = await Task.objects.select_related('project').aget(id=1)

    @action
    async def modify_project(self, name: str, delay: int):
        project_id = self.task.project.id

        def _update():
            time.sleep(delay)
            project = Project.objects.get(id=project_id)
            project.name = name
            project.save()

        threading.Thread(target=_update, daemon=True).start()

Models

from django.db import models

from rxdjango.models import ReactiveModel


class Project(ReactiveModel):
    name = models.CharField(max_length=64)


class Task(ReactiveModel):
    name = models.CharField(max_length=64)
    project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name='tasks')

Serializers

from rest_framework import serializers
from reactive_model.models import Project, Task


class ProjectSerializer(serializers.ModelSerializer):
    class Meta:
        model = Project
        fields = ['id', 'name']


class TaskSerializer(serializers.ModelSerializer):
    project = ProjectSerializer()

    class Meta:
        model = Task
        fields = ['id', 'name', 'project']

Frontend

import React, { useState } from 'react';
import { useChannel } from '@rxdjango/react';
import { ReactiveModelChannel } from '../../rx/reactive_model/reactive_model.channels';
import {
  Sections,
  Button,
  TextInput,
  Row,
  Note,
  Field,
  Fields,
} from '../../components/demo';

export function ReactiveModelDemo() {
  const channel = useChannel(ReactiveModelChannel);
  const [projectName, setProjectName] = useState('');
  const [delay, setDelay] = useState('2');

  return (
    <Sections>
      <div>
        <Note>
          Modify the project name with a delay. The update happens in a background
          thread outside the channel context, demonstrating that external changes
          to a model instance are pushed reactively to the frontend.
        </Note>
        <Row>
          <TextInput
            id="reactive-model-project-name"
            label="New project name"
            value={projectName}
            onChange={setProjectName}
          />
          <TextInput
            id="reactive-model-delay"
            label="Delay (seconds)"
            value={delay}
            onChange={setDelay}
          />
          <Button
            variant="secondary"
            onClick={() => channel.modify_project(projectName, parseInt(delay, 10))}
          >
            Modify
          </Button>
        </Row>
      </div>
      <div>
        {channel.task ? (
          <Fields>
            <Field label="Task">
              {channel.task.name}
            </Field>
            <Field label="Project">
              {channel.task.project._loaded
                ? channel.task.project.name
                : 'Loading…'}
            </Field>
          </Fields>
        ) : (
          <p>
            Connecting...
          </p>
        )}
      </div>
    </Sections>
  );
}

export default ReactiveModelDemo;