mrpmorris / Fluxor

Fluxor is a zero boilerplate Flux/Redux library for Microsoft .NET and Blazor.
MIT License
1.22k stars 139 forks source link

Injecting IActionSubscriber into a service crashes the web app on initial load #434

Closed jonsaich closed 8 months ago

jonsaich commented 1 year ago

I can use IActionSubscriber in razor pages without a problem by injecting the service in.

However, when used in a scoped registered service class, this causes the application to freeze/crash on load up. I'm using the Fluxor.Blazor.Web package with Blazor WebAssembly.

mrpmorris commented 1 year ago

Can you create a repro?

uhfath commented 1 year ago

In my app I simply tried to inject it into an Effect. As a result - complete freeze on startup. Took me a while to figure that (WASM debugging is still hard).

Could be here somewhere: https://github.com/mrpmorris/Fluxor/blob/733f8b11a4963d77aa2e4f292d449178e1053d6f/Source/Lib/Fluxor/DependencyInjection/ServiceRegistration/StoreRegistration.cs#L59

Trying to GetService an Effect during constructing Store for DI: https://github.com/mrpmorris/Fluxor/blob/733f8b11a4963d77aa2e4f292d449178e1053d6f/Source/Lib/Fluxor/DependencyInjection/ServiceRegistration/StoreRegistration.cs#L41

Which in-turn is also requested for constructing IActionSubscriber: https://github.com/mrpmorris/Fluxor/blob/733f8b11a4963d77aa2e4f292d449178e1053d6f/Source/Lib/Fluxor/DependencyInjection/ServiceRegistration/StoreRegistration.cs#L36

mrpmorris commented 1 year ago

An effect shouldn't need an IActionSubscriber.

What behaviour are you trying to achieve?

uhfath commented 1 year ago

I have an effect which uses some services, which dispatch an action and subscribe to it's results, since that is the only way to wait for an action to reduce the state.

Looks like there 2 possible workaround for this:

  1. move that logic to a component (not the best one, but easiest);
  2. resolve dependencies through IServiceProvider.GetRequiredService<> inside Effect.HandleAsync (better than (1), but also not the best).
mrpmorris commented 1 year ago

This sounds like parts know too much about each other.

uhfath commented 1 year ago

I'll try to explain in more details. Inside OnInitialized of an App.razor main component a StartAppCommand action is dispatched. That action contains an Effect, which resolves all of registered IAppStartupServices.

Each IAppStartupService contains OnStartupAsync which when implemented reads some initial settings from local storage, session storage, remote services, etc. And since these operations takes time there is a 'finishing' event after each one.

So the idea was to execute all the "settings gathering services" and wait for each of them to finish without waiting for their tasks. This way the app starts without stalling and there is a way to interact with states (show some loading indicators, statuses, etc.).

But thinking this through I remembered that the first draft of this method was made using components, not effects. Those were 'stalled' inside OnInitializedAsync waiting for tasks. And now, using Effects this should not be a problem, since they are run in parallel (if that's an applicable word for WASM, of course). Perhaps those subscriptions could be removed now.

mrpmorris commented 1 year ago

Or write a Middleware.

uhfath commented 1 year ago

Not sure if Middleware suits here, but will consider, thanks.

mrpmorris commented 1 year ago

If you want to start up lots of services asynchronously on StoreInitialized then I would create a single effect to do them all. Why can't you do that?

uhfath commented 1 year ago

If you want to start up lots of services asynchronously on StoreInitialized then I would create a single effect to do them all. Why can't you do that?

That is exactly what was my last part about. First method was using raw components and now I switched to a single Effect and multiple services without actions or subscriptions.