Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot render states with typing.ForwardRef #3437

Open
guidocalvano opened this issue Jun 4, 2024 · 3 comments
Open

Cannot render states with typing.ForwardRef #3437

guidocalvano opened this issue Jun 4, 2024 · 3 comments
Labels
bug Something isn't working

Comments

@guidocalvano
Copy link

Describe the bug
I tried to render a tree, but kept getting missing attribute errors whenever I had a ForwardRef in my state.

To Reproduce
Steps to reproduce the behavior:

  • Code/Link to Repo:
class ASTNodeState(rx.Base):
    key: str = "unknown"
    type: str = "unknown"
    text: str = "loading"
    children: List["ASTNodeState"] = []


class ASTNodeView(rx.ComponentState):

    @classmethod
    def get_component(cls, children: List[ASTNodeState], key: str, type: str, text: str) -> rx.Component:

        return rx.chakra.span(rx.text(key), rx.chakra.text(type), rx.chakra.text(text),
                              rx.foreach(children, render_child))

ast_node_view = ASTNodeView.create

def render_child(item: ASTNodeState):
    # hack to make recursion possible, this leads to an infinite loop in some deep copy function
    # item.__var_type = ASTNodeState
    # hack that I tried to use to derive type of the member (by also hacking in the Var.__getattr__ function)
    # item.___actual_class = ASTNodeState
    # final hack I tried, that also didn't work
    # other_item: ASTNodeState = item
    return ast_node_view(key=item.key, type=item.type, text=item.text, children=item.children)

Expected behavior
I expected a beautiful tree structure to render...

Specifics (please complete the following information):

  • Python Version: 3.11 and 3.9 I tried
  • Reflex Version: reflex==0.5.2
  • OS: Ubuntu
  • Browser (Optional): Chrome, but the problem seems to occur in the python code.

Additional context
When I hack the __var_type to the correct class I get infinite loops. I tried to create a member with the actual class and use that to detect type, but that for some weird reason raised an exception when run (but not when evaluated in my debugger).

The end of a frustrating day...

Weird stuff...

@guidocalvano guidocalvano added the bug Something isn't working label Jun 4, 2024
@guidocalvano
Copy link
Author

Note that this means no cyclic references between states and thus no recursion. So you wouldn't be able to make redit, because redit contains a tree structure, which is recursive.

@guidocalvano
Copy link
Author

Using rx.cond also doesn't fix this...

@picklelo
Copy link
Contributor

picklelo commented Jun 5, 2024

@guidocalvano I was looking a bit into this yesterday. There's a couple fixes we need to make to the framework to fully support this, but the code below avoids the infinite recursion:

import reflex as rx
from typing import List


class ASTNodeState(rx.Base):
    key: str = "unknown"
    type: str = "unknown"
    text: str = "loading"
    children: List["ASTNodeState"] = []


class ASTNodeView(rx.ComponentState):

    @classmethod
    def get_component(
        cls, children: List[ASTNodeState], key: str, type: str, text: str
    ) -> rx.Component:
        # need to convert it to a var and specify the type.
        children = rx.Var.create(children).to(list[ASTNodeState])

        return rx.chakra.span(
            rx.text(key),
            rx.chakra.text(type),
            rx.chakra.text(text),
            rx.foreach(children, render_child),
        )


ast_node_view = ASTNodeView.create

# rx.memo pulls the function into it's own definition, avoiding the infinite recursion
@rx.memo
def ast_node(children: List[ASTNodeState], key: str, type: str, text: str):
    return ast_node_view(key=key, type=type, text=text, children=children)

def render_child(item: ASTNodeState):
    return ast_node(
        key=item.key, type=item.type, text=item.text, children=item.children
    )

def index() -> rx.Component:
    return rx.fragment(
        ast_node_view([
            ASTNodeState(key="1", type="type1", text="text1", children=[
                ASTNodeState(key="1.1", type="type1", text="text1", children=[
            ]),
            ]),
        ], key="root", type="root", text="root"),
    )


app = rx.App()
app.add_page(index)

The recursion is caused because by default Reflex evaluates all the components into one giant component for the page. Using @rx.memo pulls out the component into its own function instead of evaluating it. There's one bug we need to fix #3438 to fix the serialization and then this should work.

The @rx.memo isn't documented much because it was kind of a hack we needed. We're planning on cleaning it up and documenting it in the future.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants