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

Chaining actions #450

Open
massimotomasi opened this issue Sep 12, 2023 · 6 comments
Open

Chaining actions #450

massimotomasi opened this issue Sep 12, 2023 · 6 comments

Comments

@massimotomasi
Copy link

massimotomasi commented Sep 12, 2023

Hello,
in my use case i have to dispatch multiple actions that modifify the same state slice. How can i chain actions forcing them to be executed in a specific order?

If i try to dispatch both actions like so

Dispatcher.Dispatch(new Action1());
Dispatcher.Dispatch(new Action2());

both reducers methods (one for action) receive the same store instance wich isn't updated according to the first action's execution causing the lost of one action.

I know that i can create one ad hoc action that can manage both changes in the reduce method but i want to know if i have an alternative wich let me keep both actions separated.

Thx

@massimotomasi massimotomasi changed the title Chaining acitons Chaining actions Sep 12, 2023
@andrevlins
Copy link

Wouldn't it be a case of creating an effect that reacts to Action1 and triggers Action2?

@massimotomasi
Copy link
Author

Hi @andrevlins this could be another option but i don't want to couple the two actions and i don't need Action2 to be fired every time i fire Action1. I have some logic that decide if Action2 should be fired after Action1 and i want that logic to stick in the razor page code behind to not couple the two actions

@mrpmorris
Copy link
Owner

@massimotomasi The code you wrote should dispatch the actions sequentially. Do you have a case where this is not happening?

Is there a repro you can share, perhaps a unit test?

@massimotomasi
Copy link
Author

massimotomasi commented Oct 17, 2023

@massimotomasi The code you wrote should dispatch the actions sequentially. Do you have a case where this is not happening?

Is there a repro you can share, perhaps a unit test?

@mrpmorris Yes, it is executed sequentially but it doesn't guarantee that at the moment i handle Action2, Action1 has been completely handled and the state, wich is passed to Actions2 is updated accordingly.
The example i had was a page with a tab item inside where Action1 was OnTabChangedAction (=> update the CurrentTab property of the store) and Action2 was LoadDataFromWebServiceAction (=> Load data online using an effect and show the results)

@mrpmorris
Copy link
Owner

All reducers should have finished for Action1 before Action2 is processed.

Note that async effects are executed in the background and night not have completed before Action2 is dispatched.

If this isn't an Effect issue but simply reducers, please let me have a repro

@uhfath
Copy link
Contributor

uhfath commented Oct 18, 2023

@massimotomasi in your case I would just make something like FinishedEvent that is triggered by an effect of Action when it's finished. And in razor pages I would SubscribeToAction to this FinishedEvent.
But if you plan to make such routine often (i.e. dispatch one action right after another) it could be better to create a simple disposable class that holds a TaskCompletionSource and calls it's SetResult when an 'event' is fired. And in user code you would just await it.
Something like this:

public class AsyncDispatchBuilder : IDisposable
{
    private readonly IDispatcher _dispatcher;
    private readonly IActionSubscriber _actionSubscriber;
    private readonly TaskCompletionSource _dispatchCompletionSource = new();
    
    private bool _hasEvents;
    private bool _isDisposed;

    protected virtual void Dispose(bool disposing)
    {
        if (!_isDisposed)
        {
            if (disposing)
            {
                _actionSubscriber.UnsubscribeFromAllActions(this);
            }

            _isDisposed = true;
        }
    }

    public AsyncDispatchBuilder(
        IDispatcher dispatcher,
        IActionSubscriber actionSubscriber)
    {
        this._dispatcher = dispatcher;
        this._actionSubscriber = actionSubscriber;
    }

    public AsyncDispatchBuilder SubscribeToAction<TAction>(Action<TAction> callback = null)
        where TAction : IStateAction
    {
        _actionSubscriber.SubscribeToAction<TAction>(this, ac =>
        {
            callback?.Invoke(ac);
            _dispatchCompletionSource.SetResult();
        });

        _hasEvents = true;
        return this;
    }

    public Task DispatchActionAsync<TAction>(TAction stateAction)
        where TAction : IStateAction
    {
        if (!_hasEvents)
        {
            throw new ArgumentException("No events were subscribed");
        }

        _dispatcher.DispatchAction(stateAction);
        return _dispatchCompletionSource.Task;
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

This could be used like:

using var builder = new AsyncDispatchBuilder(Dispatcher, ActionSubscriber)
    .SubscribeToAction<Action1.FinishedEvent>()
;

await builder.DispatchActionAsync(new Action2());

But to make it complete there should be at least 2 more 'events' - OnException and OnCancelation.
Those should trigger SetException and SetCanceled of _dispatchCompletionSource.

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

4 participants