reactive-python / reactpy-django

It's React, but in Python. Now with Django integration.
https://reactive-python.github.io/reactpy-django/
MIT License
322 stars 18 forks source link

`use_mutation` and `use_query` are not async #135

Closed Archmonger closed 1 year ago

Archmonger commented 1 year ago

Current Situation

Due to Django ORM's poor async support, use_mutation and use_query rely on database_sync_to_async with thread_sensitive=True.

This unfortunately means that only one use_query call can execute at a time. We should write the use_query hook to be natively async and support async function calls.

Proposed Actions

For async mutations and query functions:

For sync mutations and query functions:


Discussed in https://github.com/idom-team/django-idom/discussions/132

Originally posted by **numpde** March 14, 2023 Consider the following component, which is displayed using `{% component ... %}` from a template in a browser tab A. On button-click, a lengthy `use_mutation` is executed. What I find suspicious is that a copy of the component (or any component, for that matter?) would not load in a parallel tab B until the mutation in A completes. ```Python from typing import Callable from django.utils.timezone import now from django_idom.hooks import use_mutation from idom import html, use_state, component def work(t0, trg: Callable): import time time.sleep(10) trg(now() - t0) @component def Main(): sleeper = use_mutation(work) elapsed = use_state(None) return html.div( html.button( { 'on_click': (lambda e: sleeper.execute(t0=now(), trg=elapsed.set_value)), 'class_name': "btn btn-primary", 'disabled': sleeper.loading, }, f"Elapsed: {elapsed.value}" if elapsed.value else "use_mutation" ), ) ```
Archmonger commented 1 year ago

@numpde Can you try using the async mutations and query support on my branch?

pip install git+https://github.com/Archmonger/django-idom@thread-sensitive
import asyncio
from typing import Callable
from django.utils.timezone import now

from django_idom.hooks import use_mutation
from idom import html, use_state, component

async def work(t0, trg: Callable):
    await asyncio.sleep(10)
    trg(now() - t0)

@component
def Main():
    sleeper = use_mutation(work)
    elapsed = use_state(None)

    return html.div(
        html.button(
            {
                'on_click': (lambda e: sleeper.execute(t0=now(), trg=elapsed.set_value)),
                'class_name': "btn btn-primary",
                'disabled': sleeper.loading,
            },
            f"Elapsed: {elapsed.value}" if elapsed.value else "use_mutation"
        ),
    )

Your old sync mutations should also work if using MutationOptions(thread_sensitive=False).