Example of Wrapping react-live-runner library

@jamestitus299 on discord was asking about how to pass the scope to react-live-runner, so I wrote a small example demonstrating some more complex wrapping functionality

import reflex as rx


class LivePreview(rx.Component):
    """React-live component."""

    library = "react-live-runner"
    tag = "LivePreview"


class LiveError(rx.Component):
    """React-live component."""

    library = "react-live-runner"
    tag = "LiveError"


class LiveEditor(rx.Component):
    """React-live component."""

    library = "react-live-runner"
    tag = "LiveEditor"


class ReactRunner(rx.Component):
    """React-live component."""

    library = "react-live-runner"
    tag = "LiveProvider"
    code: rx.Var[str]
    scope: rx.Var[dict[str, rx.Var]]

    @staticmethod
    def make_scope_var(
        name: str, library: str = "react", **import_var_kwargs
    ) -> rx.Var:
        if "tag" in import_var_kwargs:
            import_var_kwargs.setdefault("alias", name)
        else:
            import_var_kwargs.setdefault("tag", name)
        return rx.Var(
            name,
            _var_data=rx.vars.VarData(
                imports={library: rx.ImportVar(**import_var_kwargs)},
            ),
        )

    @staticmethod
    def make_react_scope_dict(*name) -> dict[str, rx.Var]:
        return {n: ReactRunner.make_scope_var(n) for n in name}

    @staticmethod
    def external_event_handler(handler, args_spec=lambda: []) -> rx.Var:
        # Hacks needed on pre-0.6.8 and until #4608 is fixed
        return rx.Var.create(
            rx.Fragment()._create_event_chain(
                value=handler,
                args_spec=args_spec,
            )
        )._replace(
            merge_var_data=rx.vars.VarData(hooks={rx.constants.Hooks.EVENTS: None})
        )


SAMPLE_CODE = """export default function C() {
    const [v, setV] = useState(0)
    const increment = useCallback(() => setV((v) => v+1))
    
    return (
        <div>
          <p>{v}</p>
          <button onClick={increment}>Bump</button>
          <RButton onClick={toggleExt}>{ext}</RButton>
          <RButton onClick={() => setExt('indeterminate')}>???</RButton>
        </div>
    )
}
"""


class LiveState(rx.State):
    ext: str = "off"

    def toggle_ext(self):
        self.ext = "on" if self.ext == "off" else "off"


def index() -> rx.Component:
    return rx.container(
        rx.vstack(
            LiveState.ext,
            ReactRunner.create(
                LivePreview.create(),
                LiveError.create(),
                LiveEditor.create(),
                code=SAMPLE_CODE,
                scope={
                    # Passing through a default import.
                    "React": ReactRunner.make_scope_var("React", is_default=True),
                    # Helper function to import react names.
                    **ReactRunner.make_react_scope_dict(
                        "useState",
                        "useEffect",
                        "useMemo",
                        "useCallback",
                    ),
                    # Pass through components with aliased names.
                    "RButton": ReactRunner.make_scope_var(
                        "RadixButton", library="@radix-ui/themes", tag="Button"
                    ),
                    # Pass through Reflex state values and event handlers into the scope.
                    "ext": LiveState.ext,
                    "setExt": ReactRunner.external_event_handler(
                        LiveState.set_ext,
                        args_spec=lambda ext: [ext],
                    ),
                    "toggleExt": ReactRunner.external_event_handler(
                        LiveState.toggle_ext
                    ),
                },
            ),
        ),
    )


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

Note that the event handlers being passed through the scope should improve greatly in the upcoming 0.6.8 release, where it can be written without the hacky helper as

"setExt": rx.Var.create(rx.EventChain.create(value=LiveState.set_ext, args_spec=lambda ext: [ext]))

:pray:

2 Likes