Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for open generic types #200

Closed
rcdailey opened this issue Jul 10, 2021 · 42 comments
Closed

Support for open generic types #200

rcdailey opened this issue Jul 10, 2021 · 42 comments

Comments

@rcdailey
Copy link

I have a set of configuration objects that I want to send around, but I don't want to implement the Action/Event/Feature/Reducer code multiple times for it. There's already enough boilerplate with these 4 concepts, doing it multiple times for every similar or same type is just a bit much IMHO.

Here's an example set of classes for what I was trying to do. This code may not work, I gave up on using Fluxor for this and instead am using an injected generic interface for this. But hopefully it communicates the general idea.

    internal record ActiveConfig<T>(T? Config) where T : IServiceConfiguration;

    internal class ActiveConfigFeature<T> : Feature<ActiveConfig<T>>
        where T : IServiceConfiguration
    {
        public override string GetName() => nameof(ActiveConfig<T>);
        protected override ActiveConfig<T> GetInitialState() => new(default);
    }

    internal static class Reducers
    {
        [ReducerMethod]
        public static ActiveConfig<T> SetActiveConfig<T>(ActiveConfig<T> state, ActiveConfig<T> action)
            where T : IServiceConfiguration => action;
    }

I'm just using this for sharing a single piece of state across the code base.

@mrpmorris
Copy link
Owner

When Fluxor initializes, it creates an instance of every registered state (via feature), every reducer (statics are wrapped in an instance), and every effect class. These are then added to a list per store (effects) or per feature (reducers).

There is no way this can be done with open generics.

The only way I can see this being possible is if I added methods in the AddFluxor(o => options to allow you to register generic effect classes, reducer classes, and features - but you'd still need to register them rather than them being automatically detected.

@ashern
Copy link

ashern commented Jul 12, 2021

I think the possibility of explicitly registering generic type instantiations would be extremely helpful, as I currently have far too much boiler-plate when I need to simply 'register' another type to work w/ my generic state.

Would it be possible to infer related effects & reducers if, for a given generic state GenericState, the library accepted the registration of only the state/type pair?

Something like AddFluxor(o=> o.RegisterGenericState(GenericState, typeof(sometype)) would be great for me.

@rcdailey
Copy link
Author

I know that Autofac can indeed do a similar thing:

var builder = new ContainerBuilder;
builder.RegisterGeneric(typeof(MyGeneric<>)).As(typeof(IMyGeneric<>));

// .. later in a DI constructor ..
MyClass(IMyGeneric<MyCustomType> myGeneric) {}

So it is able to create types based on when those types are specified. You know at DI time when an IState is requested. I would have set it up like:

[Inject]
public IState<ActiveConfig<RadarrConfig>> ActiveConfig { get; set; }

Assuming you perform some kind of validation, once you inject this you'd want to make sure you have a Feature / Reducer available with this type, right? So maybe you can take a "lazy-load" approach to where you keep open generics in a separate registry/container until they are used, and just have a separate code path for them for validation and creation.

I'm greatly oversimplifying. I apologize if it's not helpful. I'm unfortunately unfamiliar with the implementation details. I'm sure it's not this simple. But I'm hoping it offers some perspective.

@ashern
Copy link

ashern commented Jul 12, 2021

Yes, similar to that, but instead of registering a generic type for possible instantiation as:

builder.RegisterGeneric(typeof(MyGeneric<>)).As(typeof(IMyGeneric<>));

I'd like to register the concrete types that are valid for a given generic state interface.

That way Fluxor could presumably detect reducers/effects which reference the generic state, and instantiate concrete instances of reducers/effects for each registered type at load.

@mrpmorris Obviously I'm not sure of feasibility here. Happy to help investigate if useful.

@mrpmorris
Copy link
Owner

I don't use the IServiceCollection to determine which instances to create because [ReducerMethod] and [EffectMethod] can be static, and therefore I use a wrapper class that is instantiated at the point of discovery.

Maybe I could add something like

AddFluxor(o =>
  o.ScanAssemblies(.......)
  .RegisterGenericType<MyState<Whatever1>>()
  .RegisterGenericType<MyEffects<Whatever1>>()
  .RegisterGenericType<MyReducers<Whatever1>>()
  .RegisterGenericType<MyState<Whatever2>>()
  .RegisterGenericType<MyEffects<Whatever2>>()
  .RegisterGenericType<MyReducers<Whatever2>>()
);

Seems a bit verbose though. Maybe I could add an additional extension like so

AddFluxor(o =>
  o.ScanAssemblies(.......)
  .RegisterModule<MyModule<Whatever1>()
  .RegisterModule<MyModule<Whatever2>()
);

Then you'd do this

class MyModule<T> : ModuleRegistration
{
  public override void Register(Options options)
  {
    options
    .RegisterGenericType<MyState<T>>()
    .RegisterGenericType<MyEffects<T>>()
    .RegisterGenericType<MyReducers<T>>()
  }
}

@rcdailey
Copy link
Author

For the reducer and effect method attributes, I'm assuming you use reflection to find those, not the service collection. In that case, I'd think you could take the registration-free approach and build a registry in memory of every method you find. Then match the parameters to the type T you get from IState. Basically call Type.GetGenericTypeDefinition on the type passed to IState and compare that to the parameter type in the reducer method? Again I'm sure I'm over simplifying. But I think the goal should be to match these without requiring the user to register stuff. There's already enough boilerplate IMHO.

@ashern
Copy link

ashern commented Jul 14, 2021

@mrpmorris Either of those options would work well for me. The verbosity of the first one doesn't seem like too much of an issue, and either solves the root problem. The second is nice and clean, however.

Personally, I like the explicit registration and discovery-time instantiation. It prevents the library from needing to figure out anything too complicated at runtime.

@mrpmorris
Copy link
Owner

@rcdailey what do you consider to be boilerplate?

@rcdailey
Copy link
Author

rcdailey commented Jul 14, 2021

The boilerplate I'm talking about is that open generic Fluxor types need to be registered explicitly when the closed types do not. I'd prefer the open generic implementations be transparently found, which matches existing semantics. Maybe instead of boilerplate, we should call it just an inconsistency.

I'm not sure if a reflection-based search & find solution for open generics is needed. I mostly shared that approach just to communicate my point (that if possible and reasonable to do so, having Fluxor find the types would be ideal). My disclaimer has always been that I have no idea of the implementation of Fluxor, so my implementation advice is quite possibly completely unhelpful.

My overall feeling about boilerplate with Fluxor is that I'm already creating 4 types for relatively simple publish & subscribe semantics for a single value. Compared to similar logic I'd implement in Rx with BehaviorSubject, which is much much smaller. Maybe I'm the only one with this opinion. But the reason I introduced open generics in the first place, along with sharing the same type for my State and Action object, was to reduce the number of classes I had to create/maintain for this. If I have to start maintaining a list of closed generic implementations for an open generic type, it sort of defeats the purpose in my opinion.

On the flip side, I fully accept that I'm either using Fluxor for the wrong job, and/or that I haven't learned enough about Fluxor and my opinion might not be fair. But either way that's how I feel at the moment.

At the end of the day, you're solving the root problem so I won't strongly complain about this. I appreciate the fact that you're engaged in the discussion and willing to discuss solutions. Thank you for that!

@mrpmorris
Copy link
Owner

@rcdailey If you had a state MyFeature, how would you know which class types to use for T when creating the store and creating states.

@mrpmorris
Copy link
Owner

Could someone please give me a real-life example of what you need to achieve? It'll need state / reducer / effects, then I can work out the best thing to do.

@rcdailey
Copy link
Author

rcdailey commented Jul 18, 2021

Your last question got me thinking about this a bit harder. I realized there are probably complications if you have two classes deriving from Feature<T>. Which one do you use? You wouldn't really know. It's ambiguous. In that case, the registrations make it clear which open generic subclasses should accept certain types.

In my case, I made my use case a little more specific:

  1. I had a nested generic class, e.g. Feature<ActiveConfig<T>> and I always use IState<ActiveConfig<MySpecificType>>. This eliminates the ambiguity without registrations
  2. I specify type constraints on my generics that require T to implement a specific interface. This reduces the ambiguity, but does not completely eliminate it (i.e. you could have multiple open generic implementations each sharing the same constraints, in which case it would be ambiguous).

Initially my request seemed reasonable but that was only because I was thinking of a single open generic implementation of reducer/feature/etc. But you've pointed out the obvious flaw here, or at least, helped to lead me to that realization.

I think given this, the explicit registration of open generics as you proposed earlier is more than reasonable. Also it wouldn't be fair to call it an inconsistency, since the challenges (and thus requirements) are different between non-generic / closed generic implementations and open generic ones.

Thanks for patiently walking through this with me to help me understand more about the challenges. As for a real-life example, I did give you one in the first post, although I admit it is out of context. But the context is pretty simple I think: I was using Fluxor to drive events across my components representing an active configuration chosen by the user. I have one component that allows the user to select 1 configuration in a list of many. Other components depend on this selection ("Active Config") in order to know what to present to the user. Hope that helps.

@ashern
Copy link

ashern commented Jul 19, 2021

I have a few controls that this applies to in production, but I think generics are often a natural when implementing grid controls.

Let's say that you want to create a type-aware grid component MyGrid<TData>, and you'd like to instantiate a new store for each grid, GridState<TData>.

Right now, the mechanics of this work well in Fluxor, but you're required to have defined a concrete implementation for each type, effect, and reducer that you'd like use w/ your generics.

That means that I have a bunch of useless declared GridState<SomeConcreteType>, SomeEffect<SomeConcreteType> boilerplate hanging around just so that Fluxor can discover the types.

I want to just tell Fluxor that various Generic/type combos exist, so that they can be 'discovered' and instantiated without needing useless declaration files.

I'm happy to put together a simple repro if it's easier to understand what I mean.

@ashern
Copy link

ashern commented Jul 19, 2021

I had put together this repo when you had helped w/ generics before, which shows the sort of declaration that it would be nice to avoid:

https://github.com/ashern/fluxor_generic/blob/master/Store/IntReducers.cs

Instead of requiring a concrete declaration file like IntReducers.cs, it'd be nice to do the same thing with Fluxor config.

AddFluxor(o=> o.RegisterGenericReducer(GenericReducers, typeof(int)))

Likewise for effects, etc.

@mrpmorris
Copy link
Owner

We won't be able to have open generics, but I think I will be able to allow you to specify closed generics without having to create classes.

o
  .ScanAssemblies(.......)
  .RegisterGenericState<Whatever<Person>>("Name", initialState)
  .ScanType<WheverReducers<Person>>()
  .ScanType<WhateverEffects<Person>>();

Which I can simplify further later so you can create one class to register the state + reducers + effects in a generic way, and then do something like

o.Register(MyRegClass

But I'd like some concrete examples. What is ActiveConfig, what is in it, what reducers / effects would be there?

@rcdailey
Copy link
Author

Again please see the examples in my first post. I do not have any effects.

@mrpmorris
Copy link
Owner

I'm interested in what common state is in ActiveConfig, and an example of what a reducer would do to it

@mrpmorris
Copy link
Owner

@ashern I don't think I'd have state for a grid. That's a UI concern, not state.

@ashern
Copy link

ashern commented Jul 19, 2021

@mrpmorris I'm just describing a general concept. I have a collection of type-aware stuff in a couple applications that do various things, and it works well to tie them to strongly-typed instances of state which share generically-implemented effects & reducers.

It's a pretty clean pattern that's working well in production, but there's just a bit more code required than necessary to allow Fluxor to use a generic implementation with any specific type. It'd be nice to have more direct control as you suggest over the discovery process.

No big deal either way, as it's more a developer convenience than anything.

@mrpmorris
Copy link
Owner

@ashern I understand the requirement, I am just trying to discover a real-life use case. If you could give me a real example, I'd appreciate it.

@ashern
Copy link

ashern commented Jul 20, 2021

I appreciate your engagement, @mrpmorris. It's tough for me to give too deep a real life example without getting more into the weeds about projects than I'm able to. I apologize that these are somewhat canned examples.

Let's say that you have an existing ecosystem of well defined types representing different business objects (financial instruments, for example). Let's also assume that it's a natural, from the business side, to maintain an equivalent state for each different type, such that you maintain a state object per type.

In the simplest form, each state might contain a single generic property, TInstrument SomeFinancialInstrument. You might then have a reducer to update that value and an effect to operate on that object via a service, both of which you'd like to handle in a strongly typed way, but that you'd like to only implement once for all of the various types that you'd potentially encounter.

I know this is all a bit hand-wavy. Happy to join your git chat this afternoon if you'd like to discuss further.

@rcdailey
Copy link
Author

@mrpmorris Peter, could you help me understand what a real-life example would provide you that you haven't been provided yet with the current examples? I think this would help us better understand what specific information you're looking for. For example, is there something unclear about how an Action, State, Feature, and/or Reducer would be defined in the open-generic use-case?

@mrpmorris
Copy link
Owner

mrpmorris commented Jul 21, 2021

@rcdailey It would provide me with understanding.

If I am simply told to implement solution X without understanding the requirement then I won't be confident that the solution I am implementing is ideal.

What I need to see is an example of what I would find in the generic state, what kind of generic actions there would be, and how a generic reducer would be expected to alter that state given the generic action.

When I understand what is trying to be achieved (rather than how you are trying to achieve it) then I can help.

@mrpmorris
Copy link
Owner

@ashern Thanks! Please private msg me on gitter to discuss and I will reply as soon as I can.

@brettwinters
Copy link

brettwinters commented Jul 21, 2021

sorry to hijack, but here's my own concrete example where open generics might be helpful. Although I've solved the issue with IEffect and some reflection which is working well...

the action

public record QueriedApiAction<TResult>(IQuery<TResult> Query);

And the IQuery

public interface IQuery<TResult> { }

The Generic effect

public class QueriedApiActionEffect<TResult> : Effect<QueriedApiAction<TResult>>
{
	private readonly IApiService_apiService;

	 public QueriedApiActionEffect(
		IApiService apiService)
	 {
		_apiService = apiService;
	 }
	 
	public override async Task HandleAsync(
		QueriedApiAction<TResult> action,
		IDispatcher dispatcher
	)
	{
		var result = await _apiService.FetchAsync(action.Query);
		
		dispatcher.Dispatch(
		    new ReceivedFromApiAction<TResult>(result)
		);
	}
}

My http service

    public interface IApiService
    {
        Task<TResult> FetchAsync<TResult>(IQuery<TResult> query) ;

        Task PostAsync<TCommand>(TCommand command) ;
    }

The result get dispatched

public record ReceivedFromApiAction<TResult>(TResult Result);

Let me know if you need to see how this would be used...

@rcdailey
Copy link
Author

@rcdailey It would provide me with understanding.

If I am simply told to implement solution X without understanding the requirement then I won't be confident that the solution I am implementing is ideal.

What I need to see is an example of what I would find in the generic state, what kind of generic actions there would be, and how a generic reducer would be expected to alter that state given the generic action.

When I understand what is trying to be achieved (rather than how you are trying to achieve it) then I can help.

So my "real" example would be what I put in my OP. Here it is again:

    internal record ActiveConfig<T>(T? Config) where T : IServiceConfiguration;

    internal class ActiveConfigFeature<T> : Feature<ActiveConfig<T>>
        where T : IServiceConfiguration
    {
        public override string GetName() => nameof(ActiveConfig<T>);
        protected override ActiveConfig<T> GetInitialState() => new(default);
    }

    internal static class Reducers
    {
        [ReducerMethod]
        public static ActiveConfig<T> SetActiveConfig<T>(ActiveConfig<T> state, ActiveConfig<T> action)
            where T : IServiceConfiguration => action;
    }

This obviously doesn't compile, but rather than how I'm doing it, this shows you what I'm doing as well. It's a very simple case. My state, ActiveConfig<T> (which also doubles as my action), holds a single value Config. The reducer does not transform that state; it simply "re-assigns" it (by returning the action as the new state). How I plan to use this in my application is by declaring and injecting IState<ActiveConfig<MySpecificConfigTypes>> and then using that to both acquire the current state and update it by using a dispatcher, if needed.

I really don't know how else I can explain my situation to you. Maybe my case is too simple to be useful. Hopefully the others have a more sophisticated example.

@mrpmorris
Copy link
Owner

@rcdailey you are dispatching a state object as an action? You should create a proper action like "ReplaceStateAction" or something, so it describes what your intentions are. Otherwise, what does it mean to Person a Person, or Car a Car? ReplaceCarAction would make sense.

This confusion is why I thought you were being illustrative.

@rcdailey
Copy link
Author

Not exactly. I'm dispatching an action. The action and the state happen to be the same object because I was eliminating duplicate code and reducing boilerplate. I'm expecting that the only time the state comes into play was during reducer processing. But I'm also unsure if it would break Fluxor to have them both be the same object.

@mrpmorris
Copy link
Owner

@rcdailey

"The action and the state happen to be the same object....also unsure if it would break Fluxor to have them both be the same object"

It won't break Fluxor at all, but it's not very intuitive. It's like trying to PurchaseOrder(verb) a PurchaseOrder(noun) when really you want to ReplaceConfigAction(verb) ActiveConfig(noun).

Your code was confusing, which is why I thought it wasn't "real", especially when you also said it won't compile.

mrpmorris added a commit that referenced this issue Jul 28, 2021
@mrpmorris
Copy link
Owner

mrpmorris commented Jul 28, 2021

I've started to put something together that covers features + reducers + effects, but I am still unsure as to a use....

https://github.com/mrpmorris/Fluxor/tree/200/PeteM-Generics/Tutorials/01-BasicConcepts/01F-Generics/Generics/Store

I really need a simple stand-alone example that makes sense before I could merge this into master and release it.

@mrpmorris
Copy link
Owner

mrpmorris commented Aug 5, 2021

@ashern Do you have any real-life examples? Although the code is done, I am reluctant to add complexity without seeing a good reason to add generics.

@rcdailey You could achieve what you want if you had a single feature state for configs. Have your generic action descend from a non-generic abstract class - you could then have your single reducer react to that non-generic class and call a method on the action to see what key you want to use to update the value in a dictionary.

public abstract class UpdateConfigAction
{
  public abstract Type GetConfigType();
  public abstract object GetValue();
}

public abstract class UpdateConfigAction<T>
{
  private readonly T Value;
  
  public override Type GeConfigType() => typeof(T);
  public override object GetValue() => Value;
  
  public UpdateConfig(T value)
  {
    Value = value;
  }
}

public static class ConfigReducers
{
  [ReducerMethod]
  public static ConfigState Reduce(ConfigState state, UpdateConfigAction action) =>
    new ConfigState(a new state where action.GetConfigType has been used as the key to store ation.GetValue);
}

@ashern
Copy link

ashern commented Aug 11, 2021

@mrpmorris Hi Peter, sorry for the delay, just back from vacation.

From my perspective, it potentially makes sense to use generic objects in state anywhere that you might use them regularly. Those use cases tend to be domain-specific.

For example-

Let's say that I own a chain of discount stores, and I have existing libraries to handle the back-end logic of managing the stores. I own 3 separate stores, and along with selling misc. merchandise, each store has idiosyncratic services that it offers to the public. One operates a bait and tackle department, which needs special functionality for live bait. One operates a sausage grill, which needs special functionality to account for spoilage, etc. All of this idiosyncratic functionality exposes different idiosyncratic properties for each physical location.

To keep track of the properties of each discount store, there's a poco object representing each. There's a base object, DiscountStore which tracks properties common to all stores, and each store with idiosyncratic needs has a defined child-type derived from DiscountStore; BaitAndTackleStore, SausageGrillStore, etc.

If I wanted to put a new web front end on my system, it might make sense to have a separate Fluxor state for each of my store locations of some generic state type DiscountStoreState<TIdiosyncraticStore>. That state type could keep track of aspects of the GUI state which is shared between the store locations, and also any idiosyncratic state contained in the TIdiosyncraticStore object type.

Hopefully that's a satisfactory example. Generic functionality is useful anywhere you'd like to share state functionality across different types.

@mrpmorris
Copy link
Owner

@ashern
Copy link

ashern commented Aug 13, 2021

Thank you, @mrpmorris. I'll run it through some tests this weekend.

@mrpmorris
Copy link
Owner

@ashern I think this PR should support your requirement.
#218

When you do AddFluxor(o => o.ScanAssemblies(.....)) you can also do o.ScanTypes(typeof(Meh), typeof(Meh)) etc

@jgood-sb
Copy link

jgood-sb commented Nov 2, 2021

Hey @mrpmorris, this update looks awesome, any thoughts on when you may release 4.2? Thanks!

@mrpmorris
Copy link
Owner

I'm close, there are a couple of largish changes I want to get in first. #220 #221

@ABIOLI-COM
Copy link

I would like to reopen this thread, to contribute a little bit, and to motivate to better solutions than mine... :-)

The author asked for a realistic scenario where we would need to simplify what another user called 'the boilerplate', or maybe better, the 'ceremony' to setup, sometimes, repetitive and similar scenarios in the same application.

So I'm giving all of you one, very rough, that I wrote in my spare time (over time I want to add other features, but for the moment I think it's better to show it in total simplicity).

The situation is then the following, typical web application in a Web API scenario.

We have a class Customer, whose details are not important, just like we will have Invoices, Users, and many different entities (on a database-backed server side application). We want to implement the remote request as a Fluxor state, and we want also to repeat this process for all the entities we want to use client-side.

This is what I came up with, to create a generic layer:

/*****************************/
/* File GenericRemoteCall.cs */
/*****************************/

public class RC<TClass>
{
    public record State(Arr<TClass> Elements, string? Error, bool Loaded, bool Loading)
    {
        public bool IsSuccess => !string.IsNullOrEmpty(Error);
    }
    public class Feature : Feature<State>
    {
        public override string GetName() => nameof(TClass);

        protected override State GetInitialState()
        {
            return new State(Arr.empty<TClass>(), string.Empty, false, false);
        }
    }

    public record SetAction(Arr<TClass> Elements, string? Error) { }
    public record LoadingAction(bool IsLoading) { }
    public record LoadAction() { }

    public static class Reducers
    {
        [ReducerMethod]
        [SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "<Pending>")]
        public static State OnLoading(State state, LoadingAction action)
        {
            return state with
            {
                Loading = action.IsLoading
            };
        }

        [ReducerMethod]
        [SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "<Pending>")]
        public static State OnSet(State state, SetAction action)
        {
            return state with
            {
                Elements = action.Elements,
                Error = action.Error,
                Loaded = true
            };
        }
    }

    public class Effects
    {
        public static Func<HttpClient, Task<TClass[]?>> RemoteCall { get; set; } = default!;

        private readonly HttpClient _http;
        public Effects(HttpClient Http)
        {
            _http = Http;
        }

        [EffectMethod]
        [SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "<Pending>")]
        public async Task LoadCustomers(LoadAction _, IDispatcher dispatcher)
        {
            dispatcher.Dispatch(new LoadingAction(true));
            dispatcher.Dispatch(new SetAction(await RemoteCall(_http).ConfigureAwait(false), string.Empty));
            dispatcher.Dispatch(new LoadingAction(false));
        }
    }
}

public static class RemoteCallRegistration
{
    public static FluxorOptions RegisterFluxorRemoteCall<TClass>(this FluxorOptions options, Func<HttpClient, Task<TClass[]?>> remoteCall)
    {
        RC<TClass>.Effects.RemoteCall = remoteCall;
        return options.ScanTypes(
            typeof(RC<TClass>),
            typeof(RC<TClass>.State),
            typeof(RC<TClass>.Feature),
            typeof(RC<TClass>.SetAction),
            typeof(RC<TClass>.LoadingAction),
            typeof(RC<TClass>.LoadAction),
            typeof(RC<TClass>.Reducers),
            typeof(RC<TClass>.Effects)
        );
    }
}

As you can see, I used (of course) the support offered by ScanTypes().

Given this implementation, it's now easy to use it quickly with different entity types, as in:

builder.Services
    .AddFluxor(o => o.ScanAssemblies(typeof(Program).Assembly)
    .RegisterFluxorRemoteCall<Customer>(
        async (http) => await http.GetFromJsonAsync<Customer[]>("api/customers").ConfigureAwait(false))
    .RegisterFluxorRemoteCall<Invoice>(
        async (http) => await http.GetFromJsonAsync<Customer[]>("api/invoices").ConfigureAwait(false))
    .RegisterFluxorRemoteCall<User>(
        async (http) => await http.GetFromJsonAsync<Customer[]>("api/user").ConfigureAwait(false))
    .UseReduxDevTools());

We don't need to repeat any other code at all, so the boilerplate/ceremony is really kept at a minimum.

In a blazor page we can then use these services like in:

    [Inject] IState<RC<Customer>.State> CustomersState { get; set; } = default!;
    private Arr<Customer> customersResult => CustomersState.Value.Elements;

Resulting finally in a totally strongly-typed code.

There is still a lot of work to do, to bring this code to production level, but I think that it's a good starting point: I welcome anyone that wants to help improving this solution, even only with ideas.

Thank you everybody for reading this.

@ABIOLI-COM
Copy link

...and of course an example of starting the load process is in the following:

Dispatcher.Dispatch(new RC<Customer>.LoadAction());

@robertmrobo
Copy link

...and of course an example of starting the load process is in the following:

Dispatcher.Dispatch(new RC<Customer>.LoadAction());

Is this currently supported? I'm having issues where my reducers are not being called by the dispatcher. Currently just searching on the web to see if I can find any solution before asking for help.

@mrpmorris
Copy link
Owner

Hi

Did you add calls to ScanTypes for the reducer and effect classes?

@ABIOLI-COM
Copy link

I wrote it, and I got it working easily. I'm trying to bring the code to a full library, but I don't have much time, so I'm very slow. Let me know if it doesnt' work yet, I will try to help...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants