Update input value pending validation

I’m trying to update an input pending validation. If the validation fails it should revert to the old value. My code seems to work if I type slowly, but if I type a number like 300000 somewhat fast then the toast still works warning that it is invalid but the input remains at 300000. The text appears to update ok. Is there a bug or something I can do to make it work?

class ValidationErrorState(rx.State):
    my_var: float = 0.0
    _old_value: float = 0.0

    def run_validation(self, value: str):
        print("Value: ", value)
        if float(value) > 100:
            raise ValueError("Value must be less than 100")

    def my_var_on_focus(self, value: str):
        self._old_value = float(value)

    def my_var_on_change(self, value: str):
        try:
            self.run_validation(value)
            self.my_var = float(value)
        except ValueError as e:
            self.my_var = self._old_value
            yield rx.toast(str(e))

@template(route="/error")
def validation_error():
    return rx.card(
        rx.vstack(
            rx.debounce_input(
                rx.input(
                    value=ValidationErrorState.my_var,
                    on_focus=lambda value: ValidationErrorState.my_var_on_focus(value),
                    on_change=lambda value: ValidationErrorState.my_var_on_change(value),
                    width="6rem",
                    size="1",   
                ),
                debounce_timeout=1000,
            ),
            rx.text(ValidationErrorState.my_var.to(str)),
        )
    )

The problem here is probably due to the debounce keeping its own internal buffer disconnected from the event handler and state var.

Here’s an idea: allow the textbox to have the invalid value, but indicate that it’s invalid with some kind of style feedback, like a red outline. This avoids “jank” when typing where the backend tries to overwrite the value that was entered. If the user enters an invalid value, give them the toast and don’t save the entered value to the state…but the text box can still display the invalid value, allowing the user to modify their entry.

import reflex as rx


class ValidationErrorState(rx.State):
    my_var: float = 0.0
    my_var_is_valid: bool = True

    def run_validation(self, value: str):
        print("Value: ", value, end=" ")
        if float(value) > 100:
            print("REJECT")
            raise ValueError("Value must be less than 100")
        print("Allow")

    def my_var_on_change(self, value: str):
        try:
            self.run_validation(value)
            self.my_var = float(value)
            self.my_var_is_valid = True
        except ValueError as e:
            self.my_var_is_valid = False
            yield rx.toast(str(e))


@rx.page(route="/")
def validation_error():
    return rx.card(
        rx.vstack(
            rx.debounce_input(
                rx.input(
                    value=ValidationErrorState.my_var,
                    on_change=lambda value: ValidationErrorState.my_var_on_change(value),
                    outline=rx.cond(
                        ValidationErrorState.my_var_is_valid,
                        "none",
                        "1px solid red",
                    ),
                    width="6rem",
                    size="1",   
                ),
                debounce_timeout=1000,
            ),
            rx.text(ValidationErrorState.my_var.to(str)),
        )
    )


app = rx.App()

Thank you for the idea! I’ll work with that.