Closed uhfath closed 2 years ago
The subscribe is really for grabbing hold of objects that don't get reduced into state. What are you trying to achieve?
There are some parts of code which depend on the state and on some data from backend. The issue is that querying data from backend also changes the state and I need to compare these at some point. I guess the main question is will the state guaranteed to be reduced from the specific action by the time the effect is called with the same action?
No, the callbacks are made before the state changes. I don't know if people currently depend on that to do the opposite of what you are doing.
Can you explain your scenario more? What is the exact business requirement?
There is a page with a datagrid and some filtering capabilities. This is the state which is used:
[FeatureState]
public record ClientListState
{
public bool IsLoading { get; init; }
public string SearchText { get; init; }
public int PageIndex { get; init; }
public int PageSize { get; init; }
public string SortLabel { get; init; }
public ColumnSortDirection.Direction SortDirection { get; init; }
public RemoteData<Logic.Features.Clients.Models.ClientShort> Clients { get; init; } = new();
}
Here is an action used to query the data:
public class ClientListQuery
{
public string SearchText { get; }
public int PageIndex { get; }
public int PageSize { get; }
public string SortLabel { get; }
public ColumnSortDirection.Direction SortDirection { get; }
public ClientListQuery(
string searchText,
int pageIndex,
int pageSize,
string sortLabel,
ColumnSortDirection.Direction sortDirection,
CancellationToken cancellationToken = default)
: base(cancellationToken)
{
if (string.IsNullOrWhiteSpace(sortLabel))
{
throw new ArgumentException($"'{nameof(sortLabel)}' cannot be null or whitespace.", nameof(sortLabel));
}
SearchText = searchText;
PageIndex = pageIndex;
PageSize = pageSize;
SortLabel = sortLabel;
SortDirection = sortDirection;
}
}
Here is the reducer for this action:
private static class Reducers
{
[ReducerMethod]
public static States.ClientListState Reduce(States.ClientListState clientListState, ClientListQuery clientListQuery) =>
clientListState with
{
IsLoading = true,
};
}
And the effect for it:
private class Effect : Effect<ClientListQuery>
{
private readonly LogicService _logicService;
public Effect(
LogicService logicService)
{
this._logicService = logicService;
}
public override async Task HandleAsync(ClientListQuery action, IDispatcher dispatcher)
{
try
{
var clients = await _logicService.QueryAsync(new Logic.Features.Clients.Queries.GetClientsQuery(
action.SearchText,
action.PageIndex - 1,
action.PageSize,
action.SortLabel,
action.SortDirection), action.CancellationToken);
dispatcher.DispatchAction(new Events.ClientListQuerySuccessEvent(
action.SearchText,
clients.PageIndex.Value + 1,
action.PageSize,
action.SortLabel,
action.SortDirection,
clients));
}
catch (OperationCanceledException ex)
{
dispatcher.DispatchAction(new Events.ClientListQueryCancelledEvent(ex));
}
catch (Exception ex)
{
dispatcher.DispatchAction(new Events.ClientListQueryFailedEvent(ex));
throw;
}
}
}
Here is an action which is dispatched when data is successfully retrieved from backend:
public class ClientListQuerySuccessEvent
{
public string SearchText { get; }
public int PageIndex { get; }
public int PageSize { get; }
public string SortLabel { get; }
public ColumnSortDirection.Direction SortDirection { get; }
public RemoteData<Logic.Features.Clients.Models.ClientShort> Clients { get; }
public ClientListQuerySuccessEvent(
string searchText,
int pageIndex,
int pageSize,
string sortLabel,
ColumnSortDirection.Direction sortDirection,
RemoteData<Logic.Features.Clients.Models.ClientShort> clients)
{
if (string.IsNullOrWhiteSpace(sortLabel))
{
throw new ArgumentException($"'{nameof(sortLabel)}' cannot be null or whitespace.", nameof(sortLabel));
}
SearchText = searchText;
PageIndex = pageIndex;
PageSize = pageSize;
SortLabel = sortLabel;
SortDirection = sortDirection;
Clients = clients ?? throw new ArgumentNullException(nameof(clients));
}
}
And it's reducer:
private static class Reducers
{
[ReducerMethod]
public static States.ClientListState Reduce(States.ClientListState clientListState, ClientListQuerySuccessEvent clientListQuerySuccessEvent) =>
clientListQuerySuccessEvent.Reduce(clientListState) with
{
IsLoading = false,
SearchText = clientListQuerySuccessEvent.SearchText,
PageIndex = clientListQuerySuccessEvent.PageIndex,
PageSize = clientListQuerySuccessEvent.PageSize,
SortLabel = clientListQuerySuccessEvent.SortLabel,
SortDirection = clientListQuerySuccessEvent.SortDirection,
Clients = clientListQuerySuccessEvent.Clients,
};
}
The idea is this:
ClientListQuery
action is dispatched. Reducer sets the table to IsLoading
state to show some spinners. The effect queries for data. When data is ready the backed sends a corrected page index along with it (and some stats, like total row count). This corrected page index is used in cases when a user browsers a last page (e.g. №50) and some other user deletes a last record from it. Now when querying there is no page №50 and we need to return the last available page, which is №49. This last correct page index is returned along with data to the client. The client then corrects it's pager to show correct page numbers.ClientListQuery
's effect finishes it dispatches ClientListQuerySuccessEvent
which reduces the state with correct data.NavigationManager.NavigateTo
through building the correct URL with query parameters. This is used when a user navigates to some page and then back to datagrid.OnParametersSet
event for the page which is also used to store the state and query dataIn order for this to work I need some callback for when data is ready which is guaranteed to fire only after state is reduced with the same data. Currently my assumption was that when effect is called the state is already reduced for the same action. But if effects and action events are not in any way in sync with reducers then this method at some point will fail.
Fixed in 5.4
Much appreciated!
Suppose there is a state
SomeDataState
and an actionQuerySomeDataAction
which has an effect that queries data from backend. This effect then dispatchesSomeDataReadyAction
action that reduces the stateSomeDataState
with the results.Also in code there is a
SubscribeToAction<SomeDataReadyAction>
in which I check the data from the effect and also the state. But by the time this event is fired theSomeDataState
is not reduced fromSomeDataReadyAction
.Am I right in assumption that in order for this to work reliably (event and state be in sync) one needs to dispatch another action, e.g.
SomeDataStateReadyAction
from withinSomeDataReadyAction
effect, copy everything fromSomeDataReadyAction
and subscribe to that? Will the state be already reduced withSomeDataReadyAction
by that time?Another way I see is to add some sort of a
StateChangedSource
enum toSomeDataState
, set it during every state reduce and subscribe to state changes instead of actions. But not sure if this method is better since it requires a lot of source types in my case.