@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]))