Repository: reflex-dev/reflex

Issue:
Add support for tuple returning from pages.
When a page returns a tuple, unwrap the tuple as the args for a container element (such as pc.box). This would allow for syntax such as
```
def index():
  return pc.text("Hello"), pc.text("World")
```



Discussion:
@Alek99 @masenf  I look for this.😊😊😊
@danik292 what is the use case for this? why would the user not just return a component?
@masenf so close it

Well, you were saying you wanted to work on it; I was just trying to better understand the scenario where it comes up before making a decision like that.
Well I think this can be useful when the user is making inputs there as recorded as tuple and after this rendered down like a map for example.
@masenf what is your opinion
Looking through the code, it seems we're already very close to supporting this:

https://github.com/reflex-dev/reflex/blob/76b8af3b42a618939c1fd7dd8af5dfaad9f5906b/reflex/app.py#L329-L330

We already wrap the returned component in an `rx.Fragment`, so it seems like this bit of code could attempt to splat the component into the fragment and if that gets a `TypeError` (not iterable), then just pass the component normally.

Maybe something like

```python
try:
    component = Fragment.create(*component)
except TypeError:
    component = Fragment.create(component)
```

I think it would work and the change doesn't seem too invasive, but we'll need tests and doc updates to go along with it.
@masenf so what should I  do?
@danik292 after discussing it with the team, we think it would be a cool feature. You may proceed with the implementation. ✔️ 
@masenf so I am going to work on it and then I open a PR



Patch:
diff --git a/reflex/app.py b/reflex/app.py
index c4dff52fe1c..7992de29163 100644
--- a/reflex/app.py
+++ b/reflex/app.py
@@ -457,6 +457,10 @@ def add_page(
         # Generate the component if it is a callable.
         component = self._generate_component(component)
 
+        # unpack components that return tuples in an rx.fragment.
+        if isinstance(component, tuple):
+            component = Fragment.create(*component)
+
         # Ensure state is enabled if this page uses state.
         if self.state is None:
             if on_load or component._has_event_triggers():


Test patch:
diff --git a/tests/test_app.py b/tests/test_app.py
index 42f7c47be9f..9ac5c3f7a96 100644
--- a/tests/test_app.py
+++ b/tests/test_app.py
@@ -1355,3 +1355,32 @@ def test_app_state_determination():
     # Referencing an event handler enables state.
     a4.add_page(rx.box(rx.button("Click", on_click=rx.console_log(""))), route="/")
     assert a4.state is not None
+
+
+def test_add_page_component_returning_tuple():
+    """Test that a component or render method returning a
+    tuple is unpacked in a Fragment.
+    """
+    app = App()
+
+    def index():
+        return rx.text("first"), rx.text("second")
+
+    def page2():
+        return (rx.text("third"),)
+
+    app.add_page(index)  # type: ignore
+    app.add_page(page2)  # type: ignore
+
+    assert isinstance((fragment_wrapper := app.pages["index"].children[0]), Fragment)
+    assert isinstance((first_text := fragment_wrapper.children[0]), Text)
+    assert str(first_text.children[0].contents) == "{`first`}"  # type: ignore
+    assert isinstance((second_text := fragment_wrapper.children[1]), Text)
+    assert str(second_text.children[0].contents) == "{`second`}"  # type: ignore
+
+    # Test page with trailing comma.
+    assert isinstance(
+        (page2_fragment_wrapper := app.pages["page2"].children[0]), Fragment
+    )
+    assert isinstance((third_text := page2_fragment_wrapper.children[0]), Text)
+    assert str(third_text.children[0].contents) == "{`third`}"  # type: ignore