Skip to content

Commit

Permalink
BREAKING CHANGE: Change signature for GetNextPageArg
Browse files Browse the repository at this point in the history
Also make LoadNextPageAsync return result, and throw if unable to fetch next page.
  • Loading branch information
Jcparkyn committed Nov 25, 2023
1 parent 7d23682 commit 4930fb1
Show file tree
Hide file tree
Showing 7 changed files with 407 additions and 24 deletions.
2 changes: 1 addition & 1 deletion samples/HackerNewsClient/Shared/InfinitePostList.razor
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<UseEndpointInfinite Endpoint="Api.GetTopStories"
Arg="Arg"
GetNextPageArg="(q, pageCount) => pageCount < q.Data?.NbPages ? q.Arg?.GetNextPageArgs() : null">
GetNextPageArg="pages => (pages[^1].Arg?.GetNextPageArgs(), pages.Count < pages[^1].Data?.NbPages)">
<ul class="post-list">
@foreach (var query in context.Pages)
{
Expand Down
69 changes: 46 additions & 23 deletions src/Phetch.Blazor/Experimental/UseEndpointInfinite.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

using Microsoft.AspNetCore.Components;
using Phetch.Core;
using System.Diagnostics;

/// <summary>
/// An experimental component for creating "infinite scroll" features.
Expand Down Expand Up @@ -39,9 +40,9 @@ public Endpoint<TArg, TResult> Endpoint
private TArg? _arg;

/// <summary>
/// The argument to supply to the query. If not supplied, the query will not be run automatically.
/// The argument to supply to the query when fetching the first page. This is required.
/// </summary>
[Parameter]
[Parameter, EditorRequired]
public TArg Arg
{
get => _arg!;
Expand All @@ -58,11 +59,38 @@ public TArg Arg
/// <summary>
/// A function to choose the Arg for the next page, based on the last page and number of pages.
/// </summary>
/// <remarks>
/// When this function is called, the following are guaranteed:
/// <list type="bullet">
/// <item/>
/// The <c>pages</c> list contains at least one page.
/// <item/>
/// The last page in the list has succeeded.
/// </list>
/// </remarks>
[Parameter, EditorRequired]
public GetNextPageFunc GetNextPageArg { get; set; } = null!;

/// <summary>
/// A function to choose the <see cref="Arg">Arg</see> for the next page, based on the last page
/// and number of pages.
/// </summary>
/// <param name="pages">
/// A list of query instances for the previous pages. When <see cref="GetNextPageArg"/> is
/// called, the following are guaranteed:
/// <list type="bullet">
/// <item/>
/// This list contains at least one page.
/// <item/>
/// The last page in the list has succeeded.
/// </list>
/// </param>
/// <returns>
/// A tuple containing the <see cref="Arg">Arg</see> for the next page, and a flag for whether
/// there are any more pages.
/// </returns>
// Using an explicit delegate here, because otherwise the compiler doesn't understand nullable return type
public delegate TArg? GetNextPageFunc(Query<TArg, TResult> lastPage, int pageCount);
public delegate (TArg? nextPageArg, bool hasNextPage) GetNextPageFunc(IReadOnlyList<Query<TArg, TResult>> pages);

private QueryOptions<TArg, TResult>? _options;

Expand All @@ -82,28 +110,23 @@ public QueryOptions<TArg, TResult>? Options
}
}

public async Task LoadNextPageAsync()
public async Task<TResult> LoadNextPageAsync()
{
var lastQuery = _queries.LastOrDefault();
if (lastQuery is null)
{
// This might make more sense as a throw, it shouldn't be possible to get here in normal use.
ResetQueries();
return;
}
Debug.Assert(_isInitialized);
var lastQuery = _queries.LastOrDefault()
?? throw new InvalidOperationException($"{nameof(LoadNextPageAsync)} was called before the component was initialized");
if (!lastQuery.IsSuccess)
{
return;
throw new InvalidOperationException("Cannot load next page because last page hasn't succeeded");
}
var nextArg = GetNextPageArg(lastQuery, _queries.Count);
if (nextArg is null)
var (nextArg, hasNextPage) = GetNextPageArg(_queries);
if (!hasNextPage)
{
return;
throw new InvalidOperationException($"Cannot load next page because {nameof(GetNextPageArg)} returned hasNextPage=false");
}
var newQuery = GetQuery(_endpoint);
_queries.Add(newQuery);
// TODO: Return SetArgAsync result
await newQuery.SetArgAsync(nextArg).ConfigureAwait(false);
return await newQuery.SetArgAsync(nextArg!).ConfigureAwait(false);
}

protected override void OnInitialized()
Expand Down Expand Up @@ -134,21 +157,21 @@ private void UpdateQueries()
for (var i = 0; i < _queries.Count; i++)
{
var query = _queries[i];
query.SetArg(nextArg);
query.SetArg(nextArg!); // TODO: Improve null handling

if (!query.IsSuccess)
{
RemoveQueriesAfter(i);
return;
}

nextArg = GetNextPageArg(query, i + 1);
if (nextArg is null)
(nextArg, var hasNextPage) = GetNextPageArg(_queries.GetRange(0, i + 1));
if (!hasNextPage)
{
RemoveQueriesAfter(i);
return;
}
var nextIsCached = _endpoint!.GetCachedQuery(nextArg) is not null;
var nextIsCached = _endpoint!.GetCachedQuery(nextArg!) is not null;

var isLastPage = i == _queries.Count - 1;
if (isLastPage && query.IsSuccess && nextIsCached)
Expand Down Expand Up @@ -207,7 +230,7 @@ internal UseEndpointInfiniteContext(UseEndpointInfinite<TArg, TResult> component
var lastPage = pages.LastOrDefault();
HasNextPage = lastPage is not null
&& lastPage.IsSuccess
&& component.GetNextPageArg(lastPage, pages.Count) != null;
&& component.GetNextPageArg(pages).hasNextPage;
}

private readonly UseEndpointInfinite<TArg, TResult> _component;
Expand All @@ -217,5 +240,5 @@ internal UseEndpointInfiniteContext(UseEndpointInfinite<TArg, TResult> component
public bool IsLoadingNextPage => Pages.Count > 0 && Pages[^1].IsFetching;

public void LoadNextPage() => _ = _component.LoadNextPageAsync();
public Task LoadNextPageAsync() => _component.LoadNextPageAsync();
public Task<TResult> LoadNextPageAsync() => _component.LoadNextPageAsync();
}
3 changes: 3 additions & 0 deletions test/Phetch.Blazor.Tests/UseEndpointInfinite/PageResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace Phetch.Blazor.Tests.UseEndpointInfinite;

public record PageResponse(int? NextCursor);
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@using Phetch.Blazor.Experimental

@code {
[Parameter, EditorRequired]
public UseEndpointInfiniteContext<int, PageResponse> Context { get; set; } = null!;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
@using Phetch.Blazor.Experimental
@inherits TestContext

@code {
RenderFragment EndpointFragment1(Endpoint<int, PageResponse> endpoint, int arg)
{
return @<UseEndpointInfinite
Endpoint="endpoint"
GetNextPageArg="pages => (pages[^1].Data!.NextCursor ?? 0, pages[^1].Data!.NextCursor is not null)"
Arg="arg"
Options="new()"
>
<UseEndpointInfiniteTestContent Context="@context"/>
</UseEndpointInfinite>;
}
}
Loading

0 comments on commit 4930fb1

Please sign in to comment.