reflex
reflex copied to clipboard
[REF-1501] Some proposals for call_script()
The current version of call_script() is too limited, as it can only defines a State.callback_function, we can't use it directly in a function.
Suppose we have the following requirement, it's hard to implement with call_script(). (It's just an improvised idea, so please ignore its practical value.)
def update_items(self):
# Get the width of a DOM from the frontend.
container_width = rx.call_script_v2("document.body.getBoundingClientRect().width")
# Use the obtained container width to determine how much data to display.
item_num = 30
if container_width < 1280:
item_num=20
I tried to make an easier-to-use function, temporarily called run_script():
import asyncio
import time
from reflex import constants
from reflex.state import StateUpdate
from reflex.event import Event, EventHandler
from reflex.utils import prerequisites
async def run_script(state, javascript_code: str, callback):
# create temporary handler function
async def handler(state, v):
# print(f'run_script() get value: {v}')
callback(v)
func_name = f"call_script_{time.time_ns()}"
# add to event_handlers, use temporary name
state.event_handlers[func_name] = EventHandler(fn=handler)
# print(f'call_script state name: {state.get_full_name()}: {state.event_handlers}')
# generate frontend event
app = getattr(prerequisites.get_app(), constants.CompileVars.APP)
events = [Event(
token=state.router.session.client_token,
name='_call_script',
router_data={},
payload={
'javascript_code': javascript_code,
'callback':
f'''(_eval_result) => queueEvents([Event("{state.get_full_name()}.{func_name}", {{v:_eval_result}})], socket)'''
}
)]
await app.event_namespace.emit_update(
update=StateUpdate(events=events),
sid=state.router.session.session_id,
)
Usage:
class TestState(BaseState):
async def run_script_test(self):
def callback(data):
print(f'run_script callback> data: {data}')
await run_script(self, f"document.body.getBoundingClientRect().width", callback)
(This is an unfinished version, I haven't been able to make it return a value directly, so it needs a callback instead, and no doubt the dev team can make a better version.)
I sincerely hope the Reflex team can consider these proposals, thanks.
Interesting approach, appreciate the sample code and good description.
I think this could work, the main difference to make run_script return the value is to have the internal callback function set the result of an asyncio.Future that the outer function awaits after emitting the update. As long as run_script is only ever called from a state event handler, then it should work.
A few other cleanups are necessary, like removing the temporary handler from event_handlers dict, but i don't immediately see anything that's a deal breaker from a reflex architecture perspective. I would probably implement the logic as rx.State.call_script so everything stays within rx.State.
rx.State.call_script would be great, thanks for your hard work.