reflex-dev / reflex

🕸️ Web apps in pure Python 🐍

Home Page:https://reflex.dev

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[REF-2586] on_key_down not called for input wrapped in a debounce_input.

nevdelap opened this issue · comments

Greetings all. Updating an app from v0.3.2 to the latest I found that on_key_down is not called for an input wrapped in a debounce_input. This shows how to reproduce, and a range of commits where the problem began.

Install An Environment With v0.3.2 And Run A Minimal Test To Show The Original Behaviour

Python and Reflex Versions: shown below.
OS: Arch Linux with all latest updates.
Browser: Chrome 123

conda deactivate
conda env remove -yn test
conda create -yqn test python=3.12
conda activate test

mkdir test &&
cd test &&
git clone git@github.com:reflex-dev/reflex.git &&
cd reflex &&
git checkout v0.3.2 &&
cd .. &&
pip install -e reflex &&
reflex init # Accept blank template.

Replace the content of test/test.py with this...

import reflex as rx


class State(rx.State):
    something: str = ""

    def on_key_down(self, key):
        print(key)


def index() -> rx.Component:
    return rx.box(
        rx.text(f"Reflex version: {rx.constants.Reflex.VERSION}"),
        rx.debounce_input(
            rx.input(
                on_change=State.set_something,
                on_key_down=State.on_key_down,
            ),
            debounce_timeout=250,
            value=State.something,
        ),
    )


app = rx.App()
app.add_page(index)
app.compile()
reflex init && reflex run

Type in the input, the console shows the keys being printed.

Update To The Latest To Show It Not Working That Way Anymore

^C
cd reflex &&
git checkout v0.4.7 &&
cd .. &&
pip install -e reflex &&
reflex init && reflex run

Type in the input, the console no longer shows the keys being printed.

Find The Version Where The Change Happened

^C
cd reflex &&
git checkout <different versions> &&
cd .. &&
pip install -e reflex &&
reflex init && reflex run

Until finding that v0.3.4 works, v0.3.5 has the issue.

Narrow Down To A Range Of Commits Where The Problem Was Introduced

The problem is somewhere between these two commits.

0c55723d [Tue Nov 28 18:04:07 2023] (HEAD, tag: bad) [REF-1158] Move chakra-only deps to chakra lib (#2171) [GitHub]
ee87e62e [Tue Nov 28 12:17:53 2023] [REF-1035] Track ComputedVar dependency per class (#2067) [GitHub]
626357ed [Tue Nov 28 12:09:41 2023] Memoize markdown component_map (#2219) [GitHub]
065b1b88 [Tue Nov 28 11:59:35 2023] Add index on tabs (#2225) [GitHub]
527437cf [Tue Nov 28 10:05:59 2023] [REF-144] Add context in each component to prevent rerenders (#2198) [GitHub]
ed5b3818 [Tue Nov 28 08:04:55 2023] (tag: good) update removal version of deprecated features (#2224) [GitHub]

See Why It Is Somehow Related To Debounce

Comment out the debounce like this, while running a version that doesn't work.

def index() -> rx.Component:
    return rx.box(
        rx.text(f"Reflex version: {rx.constants.Reflex.VERSION}"),
        # rx.debounce_input(
            rx.input(
                on_change=State.set_something,
                on_key_down=State.on_key_down,
            # ),
            # debounce_timeout=250,
            # value=State.something,
        ),
    )

Type in the input, the console shows the keys being printed.

Without the debounce it works in all versions.

But Another Wrinkle Even Without The Debounce

Uncomment the value=, so that the input is updated when State.something is updated.

def index() -> rx.Component:
    return rx.box(
        rx.text(f"Reflex version: {rx.constants.Reflex.VERSION}"),
        # rx.debounce_input(
            rx.input(
                on_change=State.set_something,
                on_key_down=State.on_key_down,
            # ),
            # debounce_timeout=250,
            value=State.something,
        ),
    )

Type in the input, the console again doesn't show the keys being printed.

REF-2586

Related to #2597

Thanks for reporting. When I update this method within debounce.py it fixes it.

def get_event_triggers(self) -> dict[str, Any]:
        return {
            **super().get_event_triggers(),
            EventTriggers.ON_CHANGE: lambda e0: [e0.value],
            EventTriggers.ON_KEY_DOWN: lambda e0: [e0.key],  # not present at the moment
        }

I think we need to make sure we're adding all the child event triggers to the debounce input. Though I'm unsure if this will actually debounce the on_key_change.

What is your use case for on_key_down / on_key_up? Handling individual keystrokes on the backend is definitely going to be laggy.

If you want a text_area that can submit a form when enter is pressed (and insert newlines when shift-enter is pressed), have a look at the new enter_key_submit prop on the textarea (#2884).

I think we need to make sure we're adding all the child event triggers to the debounce input.

I think this can happen after calling super().create(...), any event handlers defined in child.event_triggers that are not in the component.event_triggers should just get copied over.

Currently the code has

        component = super().create(**props)
        component._get_style = child._get_style
        return component

And I suppose it should also have something like:

    component.event_triggers.update({event: trigger for child.event_triggers.items() if event not in component.event_triggers)

(Although i haven't tested it yet)


Though I'm unsure if this will actually debounce the on_key_change.

It wont. I think we need something like rx.throttle that we can put per-event-trigger to throttle events to the backend to solve this in the general case (for mouse, scroll, key, etc events)

My use case is submitting on Ctrl+Enter. The way I'd done it is quite ugly because it tracks the state of the Ctrl key, so it'll be good if what you reference makes it nicer. My thing isn't a form, doesn't have a submit button, rather it does its work by responding to on_click on buttons and sending the index from the rx.foreach that rendered that part of the page. I'll have to figure out if I can rejig it. Thanks!

Update: Yup, that works, with the debounce temporarily commented out. 👍

With this, to call the same thing the on_click calls. Maybe it's a bit hacky, but it saves me reworking too much.

on_submit=lambda _form_data_unused: State.send_edited_prompt(index),

I might have spoken too soon. Does this look like a side effect of me doing something non-standard with the on_submit and I need to not do it that way? Or is it maybe a bug?

image

❤️