API Data Fetching Example
This example demonstrates how to fetch data from a remote API and manage the asynchronous flow using Ducky in a Blazor WebAssembly app.
1. Register Services
In Program.cs, register HttpClient, your API service, and Ducky:
// Add services to the container.
builder.Services.AddScoped(_ => new HttpClient
{
BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)
});
builder.Services.AddTransient<IMoviesService, MoviesService>();
// Add Ducky
builder.Services.AddDucky(builder.Configuration);
// Run the app
await builder.Build().RunAsync();
2. Define State & Actions
Before defining actions, specify your slice state shape without defaults. Initial values will be provided in the reducer:
public record MoviesState(
bool IsLoading,
ValueCollection<Movie> Movies,
int TotalItems,
string? ErrorMessage
);
IsLoadingindicates when a fetch is in progress.Moviesholds the current list of movies.TotalItemstracks the total available count.ErrorMessagestores any error message.
[DuckyAction]
public record FetchMovies(
int PageNumber = 1,
int PageSize = 5);
[DuckyAction]
public record FetchMoviesSuccess(
ValueCollection<Movie> Movies
int TotalItems);
[DuckyAction]
public record FetchMoviesFailure(
string ErrorMessage);
3. Reducers
public class MoviesReducers : SliceReducers<MoviesState>
{
public MoviesReducers()
{
On<FetchMovies>(Reduce);
On<FetchMoviesSuccess>(Reduce);
On<FetchMoviesFailure>(Reduce);
}
// Initialize all state properties here
public override MoviesState GetInitialState()
=> new MoviesState(
IsLoading: false,
Movies: [],
TotalItems: 0,
ErrorMessage: null
);
private static MoviesState Reduce(MoviesState state, FetchMovies action)
=> state with
{
IsLoading = true,
ErrorMessage = null
};
private static MoviesState Reduce(MoviesState state, FetchMoviesSuccess action)
=> state with
{
IsLoading = false,
Movies = action.Movies,
TotalItems = action.TotalItems,
ErrorMessage = null
};
private static MoviesState Reduce(MoviesState state, FetchMoviesFailure action)
=> state with
{
IsLoading = false,
ErrorMessage = action.ErrorMessage
};
}
4. Effects
public class FetchMoviesEffect(IMoviesService moviesService)
: AsyncEffect<FetchMovies>
{
public override async Task HandleAsync(FetchMovies action, IRootState rootState)
{
try
{
var response = await moviesService.GetMoviesAsync(action.PageNumber, action.PageSize);
Dispatcher.FetchMoviesSuccess(response.Movies, response.TotalItems);
}
catch (Exception ex)
{
Dispatcher.FetchMoviesFailure(ex.Message);
}
}
}
5. Using in a Blazor Component
In the Razor component, inject the store, subscribe to state updates for automatic re-rendering, and dispatch the fetch action on init:
@page "/movies"
@using Ducky.Blazor
@using Demo.BlazorWasm.AppStore
@inherits DuckyComponent<MoviesState>
<h3>Movie List</h3>
<button @onclick="() => Dispatcher.FetchMovies()">Refresh</button>
@if (State.IsLoading)
{
<p>Loading movies...</p>
}
else if (!string.IsNullOrEmpty(State.ErrorMessage))
{
<p class="text-danger">Error: @State.ErrorMessage</p>
}
else
{
<p>Total movies: @State.TotalItems</p>
<ul>
@foreach (var movie in State.Movies)
{
<li>@movie.Title (@movie.ReleaseYear)</li>
}
</ul>
}
@code {
// This method is called after the component is subscribed to the state
protected override void OnAfterSubscribed()
{
// Dispatch initial load
Dispatcher.FetchMovies();
}
}
This ensures:
The component reacts to state changes and re-renders.
Loading, error, and data states display correctly.
6. Run the Demo
dotnet run --project src/demo/Demo.BlazorWasm
Last modified: 17 April 2025