What is Stream Rendering and Why do you need It?
In Blazor, components load data to display. However, when a component fetches data from the server, it can freeze the UI until the data is loaded. This results in poor user experience (UX) and is considered bad practice. This is where Stream Rendering comes to the rescue.
Imagine dividing a component into two parts: one part loads while data is being fetched, and the other part loads once the data is ready. This means you can load portions of the page that do not depend on the data, allowing them to load quickly while the other part of the component handles the heavy asynchronous calls. Once the data is ready, the component re-renders with the new data.
The Problem
So now we know the problem. When fetching data asynchronously in a Blazor component, the UI thread may be blocked, leading to a poor user experience. Consider the following example of a Weather component.
@page "/weather"
@if (forecasts != null)
{
<table>
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
@foreach (var forecast in forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
}
@code {
private WeatherForecast[]? forecasts;
protected override async Task OnInitializedAsync()
{
// Simulate asynchronous loading to demonstrate streaming rendering
await Task.Delay(5000);
// Your data fetching logic here
}
private class WeatherForecast
{
// Your WeatherForecast properties here
}
}
Code snippet 1: Weather. razor
In this example, the OnInitializedAsync() method simulates a delay of 5 seconds to mimic asynchronous data loading. During this time, the UI thread is blocked, making the component appear unresponsive. The user has to wait for the entire 5 seconds before any data is displayed, which can be frustrating. Check the following output and you'll feel my frustration.
Output 1. Blocking UI Thread Until the Data is Loaded
The Solution StreamRendering
Let's use the StreamRendering attribute to render partially and progressively while data is being loaded. This means the UI can remain responsive and provide feedback to the user during data fetching. We can consider this as incremental rendering: the first rendering is the initial rendering when data is not yet available, followed by incremental rendering with the data.
You just need to plug in the following attribute at the top of your component.
@attribute [StreamRendering]
Code Snippet 2: Adding StreamRendering to Weather.razor
Now we need to add that initial rendering, which we will include in the else part of the code, where @if (forecasts == null).
@page "/weather"
@attribute [StreamRendering]
@if (forecasts != null)
{
<table>
.....
</table>
}
else
{
<div>Loading...</div>
}
@code {
.....
}
Code snippet 3: Weather component with the [StreamRendering] attribute and a Loading... div.
How StreamRendering Improves the User Experience?
With StreamRendering, the component can immediately display a "Loading..." message or any other placeholder content while data is being fetched. This approach ensures that the UI remains responsive, informing the user that data is being loaded. Once the data is available, the component updates to show the weather forecasts
Output 2. Unblocking the UI Thread
Adding Shadow Effects for Enhanced UX
To further enhance the user experience, you can add shadow effects to the loading placeholder. This is a brand-new way of loading in modern web apps.
Here's an example of adding shadow effects.
@page "/weather"
@attribute [StreamRendering]
@if (forecasts != null)
{
<table>
.....
</table>
}
else
{
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
@for (int i = 0; i < 5; i++)
{
<tr class="skeleton-row">
<td class="skeleton-cell">
<div class="skeleton"></div>
</td>
<td class="skeleton-cell">
<div class="skeleton"></div>
</td>
<td class="skeleton-cell">
<div class="skeleton"></div>
</td>
<td class="skeleton-cell">
<div class="skeleton"></div>
</td>
</tr>
}
</tbody>
</table>
}
@code {
.....
}
Code snippet 4: Weather component with skeleton shadow effect.
You also need to add the following CSS to your app.css stylesheet.
.skeleton {
display: block;
height: 16px;
background-color: #e0e0e0;
border-radius: 4px;
animation: pulse 1.5s infinite ease-in-out;
width: 100%;
}
.skeleton-row {
display: table-row;
}
.skeleton-cell {
display: table-cell;
padding: 8px;
height: 20px;
width: 25%;
}
@keyframes pulse {
0% {
background-color: #e0e0e0;
}
50% {
background-color: #f0f0f0;
}
100% {
background-color: #e0e0e0;
}
}
Code snippet 5: CSS for a skeleton shadow effect
See how beautiful it looks.
Output 3. Showing Skeleton Shadow Effect While Data Is Being Loaded
Conclusion
Blazor's StreamRendering feature significantly improves the user experience by keeping the UI responsive and providing feedback during data loading. By implementing StreamRendering in your Blazor components, you can ensure an interactive experience for your users. Enhancing loading with shadow effects further improves the usability of your application.
You can grab the code from here: [GitHub]