Refreshing serverload components
Is there a way to refresh a serverload component? The first occurrence of a page event trigger for the component sends an api call, but subsequent ones do not.
I tried clearing the event before triggering it again, which does send another api call, but does not update the component.
This would be helpful for performing crud operations via modal forms, without any navigation or path changes, and where some portion of the current page needs to update.
Side-note: the ability to have the response from a form refresh the component would be even more ideal.
Also indeeded in common pages, I think it's better to add a reload option for GotoEvent or a new event named ReloadEvent. In some case, I change some details, and want to reload page after changing.
from typing import Annotated
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
from fastui import FastUI, prebuilt_html
from fastui import components as c
from fastui import events as e
from fastui.forms import fastui_form
from pydantic import BaseModel
app = FastAPI()
class Config(BaseModel):
name: str
max_num: int
config = Config(name="test", max_num=10)
@app.get("/api/config", response_model=FastUI, response_model_exclude_none=True)
def api_index():
return c.Page(
components=[
# Header
c.Heading(text="Config"),
c.Button(text="Update config", on_click=e.PageEvent(name="open")),
c.Modal(
title="Modal",
body=[
c.ModelForm(
model=Config,
initial=config.model_dump(),
submit_url="/api/config",
)
],
open_trigger=e.PageEvent(name="open"),
),
c.Details(data=config),
],
)
@app.post("/api/config", response_model=FastUI, response_model_exclude_none=True)
def api_config_update(new_config: Annotated[Config, fastui_form(Config)]):
config.name = new_config.name
config.max_num = new_config.max_num
return [
c.Json(value=config.model_dump()),
# I want to refresh the page here
# I'd like to fire a ReloadEvent rather than use GotoEvent here
c.Button(text="Back", on_click=e.GoToEvent(url="/config")),
]
@app.get("/{path:path}")
async def html_landing() -> HTMLResponse:
return HTMLResponse(prebuilt_html(title="FastUI Demo"))
@app.post("/api/config", response_model=FastUI, response_model_exclude_none=True) def api_config_update(new_config: Annotated[Config, fastui_form(Config)]): config.name = new_config.name config.max_num = new_config.max_num return [ c.Json(value=config.model_dump()), # I want to refresh the page here # I'd like to fire a ReloadEvent rather than use GotoEvent here c.Button(text="Back", on_click=e.GoToEvent(url="/config")), ]
You can "reload" the page if the url is different from the first one. So, instead of creating a page with a "back" button, you can just append a random parameter in "GoTo" event. This way, FastUI will treat it as another url:
import uuid
@app.post("/api/config", response_model=FastUI, response_model_exclude_none=True)
def api_config_update(new_config: Annotated[Config, fastui_form(Config)]):
config.name = new_config.name
config.max_num = new_config.max_num
return [c.FireEvent(event=GoToEvent(url=f"/config/?refresh={uuid.uuid4()}"))]
Thanks, it's vell usefull for me.
Yeah, it works, but not for all components. I found a bug in fastui: if the HTML structure of a new page is the same (even if it's a completely different URL), c.Video is not updated. It's strange because, in the developer tools, I can see that the src_url for the video is different, and therefore the video must differ. I spent about 8 hours trying to fix this, and all I came up with was just appending an invisible HTML toast to trigger c.Video component to update, and it worked.
So, I recommend you check whether everything is updating on your page correctly or you have to implement something like I did:
c.Div(
components=[
c.Div(
components=[
c.Details(data=ud)
],
class_name="flex-grow-1"
),
c.Div(
components=[
FastUISharedComponents.get_random_element(
refresh_id=refresh_id,
url=url
)
],
class_name="ms-4"
),
],
class_name='d-flex align-self-start'
),
class FastUISharedComponents:
@staticmethod
def get_video_or_placeholder(*,
url: str
) -> Union[c.Video, c.Text]:
return (
c.Video(sources=[url],
controls=True,
height=500,
)
if url else
c.Text(text="Video is not found")
)
@classmethod
def get_random_element(cls,
*,
refresh_id: RefreshEnum,
url: str
) -> c.Div:
if refresh_id == RefreshEnum.FIRST:
return c.Div(
components=[
cls.get_video_or_placeholder(url=url)
],
class_name="ms-4"
)
else:
return c.Div(
components=[
c.Div(
components=[c.Toast(title="This is a invisible toast",
body=[c.Text(text="This is a invisible toast")]
)
]
),
cls.get_video_or_placeholder(url=url),
],
class_name="ms-4"
)
return FastUIComponents.get_redirect_response(
redirect_url=f"/goods/{type}?refresh_id={refresh_id}"
)
The refresh_id in my case will be a bool parameter in url. If refresh_id == RefreshEnum.FIRST, then I just put c.Video component as it is. if refresh_id == RefreshEnum.SECOND, then I additionaly append c.Toast. This way, no matter how many times you refresh the page, you will get a page with a slightly different structure each time, which helps to trigger c.Video to update.
If you don't have c.Video on your page, then I suppose you don't have to do something like that