I got a bug / race when switching states I have a ...
# kvision
p
I got a bug / race when switching states I have a view that’s bind by
Copy code
bind(AppManager.store.sub { it.commercialProposalState to it.customerState }) { states ->
  commercialProposalEdit(states.first, states.second)
}
This view is shown only when
state.coreState.view == CP_EDIT
Also this view uses some fields from Then I dispatch an event that changes
state.coreState.view
and sets
state.customerState
fields to null. After that I got an exception that some field from
state.customerState
is null. But view
state.coreState.view
is already changed so my component should not update anymore. It looks like it’s updated first, and only then it is replaced by another view. It seems that this update should not happen, because the component should be disposed before it’s update could be triggered Also this breaks the app and my
commercialProposalEdit
component is never disposed and on every
state
change it throws an exception. So only reloading the page helps.
r
What do you use to keep your state?
I've managed to reproduce your issue with
ObservableValue
and with redux modules.
👌 1
It's working this way because since KVision 5.0 data binding is asynchronous by default.
The view is disposed asynchronously and that means all subscriptions from the state/store are canceled asynchronously. In this case after the state is updated and subscribe callbacks get fired. This makes your factory functions being executed even when your component is waiting to be disposed right after.
The easiest way to fix this is using
bindSync
instead of
bind
on your view top level binding. This will make your components disposed immediately when the view is changed.
Knowing the nature of this issue you can also just accept the problem and add some null checks in your component 😉
What's interesting I couldn't reproduce the problem when using
StateFlow
as the store. It's probably because the flow is also asynchronous by design.
And one more thing about your code. Please note that creating a substore by calling
sub
is registering a new subscription. In your code there is nothing that can cancel this subscription and your code is calling
sub
many times (on every state change). You are leaking memory and degrading performance here.
The
sub
function allows you to bind this subscription with a component (and the subscription will be canceled when the component is disposed). You can just pass a component as the first parameter of sub.
Copy code
bind(AppManager.store.sub(this) { it.commercialProposalState to it.customerState }) { states ->
  commercialProposalEdit(states.first, states.second)
}
Even better is to use
bind
with two parameters:
Copy code
bind(AppManager.store, { it.commercialProposalState to it.customerState }) { states ->
  commercialProposalEdit(states.first, states.second)
}
which will automatically connect store, the given function and the component for disposal and subscription cancelation
🙏 1
p
Oh wow, such a comprehensive answer, thanks. Yes, I’m using Redux and ObservableValues. That
bind
is called on a
Div
that’s created within another
bind
, which is called on
Root
Copy code
root("kvapp") {
  bind(AppManager.store.sub { it.coreState }) { coreState ->
    div(className = "page") {
      div(className = "page-content container-fluid") {
        when (coreState.view) {
          View.SUPPLIERS -> {
            bind(AppManager.store.sub { it.supplierState }) { state ->
              suppliers(state)
            }
          }
          ...
        } ...
}