Disable fully controlled Input and TextArea (#1383)
This commit is contained in:
parent
850ab43325
commit
c11d9e657f
@ -20,12 +20,17 @@ def FullyControlledInput():
|
|||||||
|
|
||||||
@app.add_page
|
@app.add_page
|
||||||
def index():
|
def index():
|
||||||
return rx.debounce_input(
|
return rx.fragment(
|
||||||
rx.input(
|
rx.debounce_input(
|
||||||
|
rx.input(
|
||||||
|
on_change=State.set_text, id="debounce_input_input" # type: ignore
|
||||||
|
),
|
||||||
value=State.text,
|
value=State.text,
|
||||||
on_change=State.set_text, # type: ignore
|
debounce_timeout=0,
|
||||||
),
|
),
|
||||||
debounce_timeout=0,
|
rx.input(value=State.text, id="value_input"),
|
||||||
|
rx.input(on_change=State.set_text, id="on_change_input"), # type: ignore
|
||||||
|
rx.button("CLEAR", on_click=rx.set_value("on_change_input", "")),
|
||||||
)
|
)
|
||||||
|
|
||||||
app.compile()
|
app.compile()
|
||||||
@ -65,14 +70,19 @@ async def test_fully_controlled_input(fully_controlled_input: AppHarness):
|
|||||||
)[0]
|
)[0]
|
||||||
|
|
||||||
# find the input and wait for it to have the initial state value
|
# find the input and wait for it to have the initial state value
|
||||||
text_input = driver.find_element(By.TAG_NAME, "input")
|
debounce_input = driver.find_element(By.ID, "debounce_input_input")
|
||||||
assert fully_controlled_input.poll_for_value(text_input) == "initial"
|
value_input = driver.find_element(By.ID, "value_input")
|
||||||
|
on_change_input = driver.find_element(By.ID, "on_change_input")
|
||||||
|
clear_button = driver.find_element(By.TAG_NAME, "button")
|
||||||
|
assert fully_controlled_input.poll_for_value(debounce_input) == "initial"
|
||||||
|
assert fully_controlled_input.poll_for_value(value_input) == "initial"
|
||||||
|
|
||||||
# move cursor to home, then to the right and type characters
|
# move cursor to home, then to the right and type characters
|
||||||
text_input.send_keys(Keys.HOME, Keys.ARROW_RIGHT)
|
debounce_input.send_keys(Keys.HOME, Keys.ARROW_RIGHT)
|
||||||
text_input.send_keys("foo")
|
debounce_input.send_keys("foo")
|
||||||
assert text_input.get_attribute("value") == "ifoonitial"
|
assert debounce_input.get_attribute("value") == "ifoonitial"
|
||||||
assert backend_state.text == "ifoonitial"
|
assert backend_state.text == "ifoonitial"
|
||||||
|
assert fully_controlled_input.poll_for_value(value_input) == "ifoonitial"
|
||||||
|
|
||||||
# clear the input on the backend
|
# clear the input on the backend
|
||||||
backend_state.text = ""
|
backend_state.text = ""
|
||||||
@ -80,12 +90,31 @@ async def test_fully_controlled_input(fully_controlled_input: AppHarness):
|
|||||||
await fully_controlled_input.emit_state_updates()
|
await fully_controlled_input.emit_state_updates()
|
||||||
assert backend_state.text == ""
|
assert backend_state.text == ""
|
||||||
assert (
|
assert (
|
||||||
fully_controlled_input.poll_for_value(text_input, exp_not_equal="ifoonitial")
|
fully_controlled_input.poll_for_value(
|
||||||
|
debounce_input, exp_not_equal="ifoonitial"
|
||||||
|
)
|
||||||
== ""
|
== ""
|
||||||
)
|
)
|
||||||
|
|
||||||
# type more characters
|
# type more characters
|
||||||
text_input.send_keys("getting testing done")
|
debounce_input.send_keys("getting testing done")
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
assert text_input.get_attribute("value") == "getting testing done"
|
assert debounce_input.get_attribute("value") == "getting testing done"
|
||||||
assert backend_state.text == "getting testing done"
|
assert backend_state.text == "getting testing done"
|
||||||
|
assert fully_controlled_input.poll_for_value(value_input) == "getting testing done"
|
||||||
|
|
||||||
|
# type into the on_change input
|
||||||
|
on_change_input.send_keys("overwrite the state")
|
||||||
|
time.sleep(0.1)
|
||||||
|
assert debounce_input.get_attribute("value") == "overwrite the state"
|
||||||
|
assert on_change_input.get_attribute("value") == "overwrite the state"
|
||||||
|
assert backend_state.text == "overwrite the state"
|
||||||
|
assert fully_controlled_input.poll_for_value(value_input) == "overwrite the state"
|
||||||
|
|
||||||
|
clear_button.click()
|
||||||
|
time.sleep(0.1)
|
||||||
|
assert on_change_input.get_attribute("value") == ""
|
||||||
|
# potential bug: clearing the on_change field doesn't itself trigger on_change
|
||||||
|
# assert backend_state.text == ""
|
||||||
|
# assert debounce_input.get_attribute("value") == ""
|
||||||
|
# assert value_input.get_attribute("value") == ""
|
||||||
|
@ -31,6 +31,9 @@ class DebounceInput(Component):
|
|||||||
# If true, notify when form control loses focus
|
# If true, notify when form control loses focus
|
||||||
force_notify_on_blur: Var[bool] = True # type: ignore
|
force_notify_on_blur: Var[bool] = True # type: ignore
|
||||||
|
|
||||||
|
# If provided, create a fully-controlled input
|
||||||
|
value: Var[str]
|
||||||
|
|
||||||
def _render(self) -> Tag:
|
def _render(self) -> Tag:
|
||||||
"""Carry first child props directly on this tag.
|
"""Carry first child props directly on this tag.
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
from reflex.components.component import EVENT_ARG
|
from reflex.components.component import EVENT_ARG, Component
|
||||||
from reflex.components.libs.chakra import ChakraComponent
|
from reflex.components.libs.chakra import ChakraComponent
|
||||||
from reflex.utils import imports
|
from reflex.utils import imports
|
||||||
from reflex.vars import ImportVar, Var
|
from reflex.vars import ImportVar, Var
|
||||||
@ -69,6 +69,28 @@ class Input(ChakraComponent):
|
|||||||
"on_key_up": EVENT_ARG.key,
|
"on_key_up": EVENT_ARG.key,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create(cls, *children, **props) -> Component:
|
||||||
|
"""Create an Input component.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
children: The children of the component.
|
||||||
|
props: The properties of the component.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The component.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: If the value is a state Var.
|
||||||
|
"""
|
||||||
|
if isinstance(props.get("value"), Var) and props.get("on_change"):
|
||||||
|
raise ValueError(
|
||||||
|
"Input value cannot be bound to a state Var with on_change handler.\n"
|
||||||
|
"Provide value prop to rx.debounce_input with rx.input as a child "
|
||||||
|
"component to create a fully controlled input."
|
||||||
|
)
|
||||||
|
return super().create(*children, **props)
|
||||||
|
|
||||||
|
|
||||||
class InputGroup(ChakraComponent):
|
class InputGroup(ChakraComponent):
|
||||||
"""The InputGroup component is a component that is used to group a set of inputs."""
|
"""The InputGroup component is a component that is used to group a set of inputs."""
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
from reflex.components.component import EVENT_ARG
|
from reflex.components.component import EVENT_ARG, Component
|
||||||
from reflex.components.libs.chakra import ChakraComponent
|
from reflex.components.libs.chakra import ChakraComponent
|
||||||
from reflex.vars import Var
|
from reflex.vars import Var
|
||||||
|
|
||||||
@ -55,3 +55,25 @@ class TextArea(ChakraComponent):
|
|||||||
"on_key_down": EVENT_ARG.key,
|
"on_key_down": EVENT_ARG.key,
|
||||||
"on_key_up": EVENT_ARG.key,
|
"on_key_up": EVENT_ARG.key,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create(cls, *children, **props) -> Component:
|
||||||
|
"""Create an Input component.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
children: The children of the component.
|
||||||
|
props: The properties of the component.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The component.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: If the value is a state Var.
|
||||||
|
"""
|
||||||
|
if isinstance(props.get("value"), Var) and props.get("on_change"):
|
||||||
|
raise ValueError(
|
||||||
|
"TextArea value cannot be bound to a state Var with on_change handler.\n"
|
||||||
|
"Provide value prop to rx.debounce_input with rx.text_area as a child "
|
||||||
|
"component to create a fully controlled input."
|
||||||
|
)
|
||||||
|
return super().create(*children, **props)
|
||||||
|
Loading…
Reference in New Issue
Block a user