Repository: reflex-dev/reflex
Pull request: [fix issue 632] textarea rendering issue
### All Submissions:

- [x] Have you followed the guidelines stated in [CONTRIBUTING.md](https://github.com/pynecone-io/pynecone/blob/main/CONTRIBUTING.md) file?
- [x] Have you checked to ensure there aren't any other open [Pull Requests](https://github.com/pynecone-io/pynecone/pulls ) for the desired changed?

### Type of change

Please delete options that are not relevant.

- [x] Bug fix (non-breaking change which fixes an issue)

### Description
In order to controll textarea, we need to synchronize the state.
However, event chain executed asynchronizing. 
So, I synchronize state before event chain.

#### Usage
no need to change py file.
```py
pc.text_area(
    value=State.area_value, 
    on_change=State.set_area_value,
)
```

The js file is changed below.

```js

// before
onChange={(_e) => Event([...chain])}

// after
onChange={(_e)=>{setState(prev =>{
  ... // set state sync
  }), 
  () => Event([...chain]) // event after set state
)}}
```

Problem statement:
How to solve `pc.text_area` state render problem?
**Describe the bug**
Hi, 
I am trying a create a fully controlled `pc.text_area`, so that can be cleared by a button.
Using `pc.text_area(value=State.area_value, on_change=State.set_area_value)`, when I move the cursor inside the text and after key up, the cusor will be auto focus at the end of the text and seems very laggy at complicated components.
Evenmore, when I key non-English words(like Chinese Pinyin Input), the input process will be interrupted at second key down and it leaves wrong string terribly.
Or using  `pc.text_area(on_blur=State.set_area_value),` I lost control of `State.area_value` and cannot clear text_area.

How to solve the above bind and state render scenario？Could we render state manually or any other solutions?
Thanks inadvance.


**To Reproduce**
```
class State(pc.State):
    """The app state."""
    area_value: str = "TestState"
    show_progress: bool = False

    def toggle_progress(self):
        self.show_progress = not self.show_progress

    def clear_area(self):
        time.sleep(2)
        self.area_value = ""

def index() -> pc.Component:
    return pc.center(
        pc.vstack(
            pc.text_area(value=State.area_value, on_change=State.set_area_value),#, 
            #pc.text_area(on_blur=State.set_area_value),#, 
            pc.hstack(
                pc.cond(
                    State.show_progress,
                    pc.circular_progress(is_indeterminate=True ),
                    pc.button("Clear", on_click=[
                        State.toggle_progress,
                        State.clear_area,
                        State.toggle_progress
                    ])
                )
            )
        ),
    )
```


** Specifics (please complete the following information):**
 - Python Version: 3.10
 - Pynecone Version: 0.1.19
 - OS: Windows
 - Browser (Optional): Edge
 
**Additional context**
Add any other context about the problem here.

Hints from issue discussion:
According to [React document](https://reactjs.org/docs/forms.html) this is the right way.  

I think thera are two reasons the cursor jumps to end of text:
1. reset the input value with state's value.  
    Textare with only `value=State.area_value` always update input.
3. update the input value with on change event.  
    if chage clear button event to updating the input with `1TestState`, when my cursor is located after `1` char it is jumped.

Kind of tricky, cause when the on change event is happeing, the state remains the same before on the other hand event string is the new one.

I try to figure out the best way though.

Commit message: fix fully controled textarea inssue.

Patch to review:
diff --git a/pynecone/compiler/templates.py b/pynecone/compiler/templates.py
index c8731f963ea..39fb89c9bdc 100644
--- a/pynecone/compiler/templates.py
+++ b/pynecone/compiler/templates.py
@@ -154,7 +154,15 @@ def format_state(
         "}})",
     ]
 ).format
-
+FULL_CONTROL = path_ops.join(
+    [
+        "{{setState(prev => ({{",
+        "...prev,{state_name}: {arg}",
+        "}}), ",
+        "()=>Event([{chain}])",
+        ")}}",
+    ]
+).format
 
 # Effects.
 ROUTER = constants.ROUTER
diff --git a/pynecone/components/component.py b/pynecone/components/component.py
index 3605419b792..65c12b796fc 100644
--- a/pynecone/components/component.py
+++ b/pynecone/components/component.py
@@ -134,7 +134,11 @@ def __init__(self, *args, **kwargs):
 
             # Check if the key is an event trigger.
             if key in triggers:
-                kwargs["event_triggers"][key] = self._create_event_chain(key, value)
+                state_name = kwargs["value"].name if kwargs.get("value", False) else ""
+                full_control = self.is_full_control(kwargs)
+                kwargs["event_triggers"][key] = self._create_event_chain(
+                    key, value, state_name, full_control
+                )
 
         # Remove any keys that were added as events.
         for key in kwargs["event_triggers"]:
@@ -167,12 +171,16 @@ def _create_event_chain(
         value: Union[
             Var, EventHandler, EventSpec, List[Union[EventHandler, EventSpec]], Callable
         ],
+        state_name: str = "",
+        full_control: bool = False,
     ) -> Union[EventChain, Var]:
         """Create an event chain from a variety of input types.
 
         Args:
             event_trigger: The event trigger to bind the chain to.
             value: The value to create the event chain from.
+            state_name: The state to be fully controlled.
+            full_control: Whether full contorolled or not.
 
         Returns:
             The event chain.
@@ -240,8 +248,13 @@ def _create_event_chain(
                 for e in events
             ]
 
+        # set state name when fully controlled input
+        state_name = state_name if full_control else ""
+
         # Return the event chain.
-        return EventChain(events=events)
+        return EventChain(
+            events=events, state_name=state_name, full_control=full_control
+        )
 
     @classmethod
     def get_triggers(cls) -> Set[str]:
@@ -466,6 +479,27 @@ def get_custom_components(
             custom_components |= child.get_custom_components(seen=seen)
         return custom_components
 
+    def is_full_control(self, kwargs: dict) -> bool:
+        """Return if the component is fully controled input.
+
+        Args:
+            kwargs: The component kwargs.
+
+        Returns:
+            Whether fully contoled.
+        """
+        value = kwargs.get("value")
+        if value is None or type(value) != BaseVar:
+            return False
+
+        on_change = kwargs.get("on_change")
+        if on_change is None or type(on_change) != EventHandler:
+            return False
+
+        value = value.full_name
+        on_change = on_change.fn.__qualname__
+        return value == on_change.replace(constants.SETTER_PREFIX, "")
+
 
 # Map from component to styling.
 ComponentStyle = Dict[Union[str, Type[Component]], Any]
diff --git a/pynecone/components/tags/tag.py b/pynecone/components/tags/tag.py
index f170a83f48e..cdd8f943482 100644
--- a/pynecone/components/tags/tag.py
+++ b/pynecone/components/tags/tag.py
@@ -78,6 +78,9 @@ def format_prop(
             if len(prop.events) == 1 and prop.events[0].upload:
                 # Special case for upload events.
                 event = format.format_upload_event(prop.events[0])
+            elif prop.full_control:
+                # Full control component events.
+                event = format.format_full_control_event(prop)
             else:
                 # All other events.
                 chain = ",".join([format.format_event(event) for event in prop.events])
diff --git a/pynecone/event.py b/pynecone/event.py
index 38b2ad63474..cce531961e8 100644
--- a/pynecone/event.py
+++ b/pynecone/event.py
@@ -110,6 +110,12 @@ class EventChain(Base):
 
     events: List[EventSpec]
 
+    # Whether events are in fully controlled input.
+    full_control: bool = False
+
+    # State name when fully controlled.
+    state_name: str = ""
+
 
 class Target(Base):
     """A Javascript event target."""
diff --git a/pynecone/utils/format.py b/pynecone/utils/format.py
index f971ab60374..1c83a602793 100644
--- a/pynecone/utils/format.py
+++ b/pynecone/utils/format.py
@@ -15,7 +15,7 @@
 from pynecone.utils import types
 
 if TYPE_CHECKING:
-    from pynecone.event import EventHandler, EventSpec
+    from pynecone.event import EventChain, EventHandler, EventSpec
 
 WRAP_MAP = {
     "{": "}",
@@ -303,6 +303,25 @@ def format_upload_event(event_spec: EventSpec) -> str:
     return f'uploadFiles({parent_state}, {templates.RESULT}, {templates.SET_RESULT}, {parent_state}.files, "{state}.{name}",UPLOAD)'
 
 
+def format_full_control_event(event_chain: EventChain) -> str:
+    """Format an fully controlled input prop.
+
+    Args:
+        event_chain: The event chain for full controlled input.
+
+    Returns:
+        The compiled event.
+    """
+    from pynecone.compiler import templates
+
+    event_spec = event_chain.events[0]
+    arg = event_spec.args[0][1]
+    state_name = event_chain.state_name
+    chain = ",".join([format_event(event) for event in event_chain.events])
+    event = templates.FULL_CONTROL(state_name=state_name, arg=arg, chain=chain)
+    return event
+
+
 def format_query_params(router_data: Dict[str, Any]) -> Dict[str, str]:
     """Convert back query params name to python-friendly case.
 


Code review comments:
diff_hunk: @@ -466,6 +479,27 @@ def get_custom_components(
             custom_components |= child.get_custom_components(seen=seen)
         return custom_components
 
+    def is_full_control(self, kwargs: dict) -> bool:
+        """Return if the component is fully controled input.
+
+        Args:
+            kwargs: The component kwargs.
+
+        Returns:
+            Whether fully contoled.
path: pynecone/components/component.py
text: @user1:
very minor typo `controlled` instead of contoled`