Open JovanVeljanoski opened 2 months ago
Before I try to dig in, I wonder if a bug I recently discovered might also play in here:
I just increment in a task. But it breaks when I hit save again, and trigger the hot reload. I first wanted to make you aware of that bug. If this problem you describe still exist, let me know, and i'll dig deeper.
I figured it out - so no need to do any deep dives.
Let me explain the solution: maybe others will make find it useful and maybe the docs can be updated to better explain this concept..
Maybe this is obvious for others, it was not for me - in fact I thought I was doing the right thing but I fell into a trap.
I define a global state for my application like this
@dataclasses.dataclass(frozen=False)
class AppState:
data: solara.Reactive[Data | None] = solara.reactive(None)
session_state: solara.Reactive[SessionState] = solara.reactive(SessionState())
settings: solara.Reactive[Settings] = solara.reactive(Settings())
The AppState
dataclass has a session_state
attribute that is reactive variable, the value of which should be another dataclass SessionState
.
Now, we know that to trigger a re-render, we can not modify reactive variables in place, but we need to pass/set a new instance or a new copy of an instance.
So in the function that resulted with the parent component not rendering i thought i was passing a new instance of SessionState
to my AppState
, BUT that new SessionState
had attributes that were references to the attributes of the old SessionState
.
In code instead of words one should NOT do this:
session_state = app_state.session_state.value
session_state.some_list_attribute.append(stuff_to_append)
new_session_state = SessionState(some_list_attribute = session_state.some_list_attribute) # and other attributes etc..
app_state.session_state.set(new_session_state)
# Does not work because `new_session_state` is a new object, BUT `some_list_attribute` is the same list from `session_state`, and this will not trigger a re-render even thought the `app_state` will be correctly updated.
Instead this works
session_state = list(app_state.session_state.value) # This creates a copy of the list, otherwise one can use copy.deepcopy
session_state.some_list_attribute.append(stuff_to_append)
new_session_state = SessionState(some_list_attribute = session_state.some_list_attribute) # and other attributes etc..
app_state.session_state.set(new_session_state)
This is a case of non-primitive data types. For primitives, python makes a copy so it is not a problem.
My error was that I thought I only need to make sure that the SessionState()
dataclass is a new object, but what you need to make sure, is that the data you pass to it is also a new, and not reference to objects in the previous "state" (value of a reactive variable).
Hope this explanation makes sense.
P.S.: An example in the docs of this case might not be a bad idea (just in case).
(This can be closed).
Is there a general (or a specific) guideline on when re-rendering of components takes place? My understanding is that, it is close to "whenever a reactive variable/object is modified, it triggers a (re)render".
But things get a bit murky (for me) when an app gets a bit more complicated, and there are many interlinked components living in different files, "state" files (reactive objects) etc..
My appologies, I tried to make a pycafe example to illustrate this, but it ended up being too complex.. so I will try to add the relevant sections of my code to illustrate the problem / confusion.
in a file called "state.py" i have define a "state" to be used across different pages of the application
Then I have a primary, large ish component that goes like this (the main parts only)
Finally i define the components in "components.py"
So basically this is happening.. When run the
Example()
will show a Start button (from theStartSessionComponent
component). Clicking the Start button makes everything behave normally. theapp_state
is being updated, and theExample()
is rerendered showing what comes next (according to the conditions).When I click the
Check
button (from theQuestionComponent
), thecheck_answer
task runs successfully, and theapp_state
is correctly updated (I can see this from the print statement). However theExample()
is not rerendered.From what I can see, the approach is basically identical between the usage of the tasks (
initialize_session
andcheck_answer
) and in both cases theapp_state
object gets updated correctly. However the former case consistently re-renders the UI, while the later never does.Aside: I know that that in both cases
app_state
is correctly updated. When I develop, i have hot-reload on, so if I make a trivial change, the app will "soft-refresh" and it will go to the next expected state, as I would expect it to do whenapp_state
is updated.I understand this is a .. long convoluted example and not something you can run to figure out what is wrong. Nor do I expect it to be a bug, but more a design/structuring/flow problem. Any advice would be helpful. Thanks!