Closed proff closed 3 years ago
true - matched false - not matched null - unknown
I think another workaround is to just define a match with the opposite breakpoint MediumUp. Then you can write something like:
@if (SmallDown)
{
// small down stuff
}
@if (MediumUp)
{
// medium up stuff
}
Had this issue to and i solved it this way as far as I remember.
I feel that this comes down to making the best assumption about which default media query will fit the largest amount of users who load the application. There's not really a third state as far as media queries are concerned.
Example:
If my application displays a mobile friendly layout <Mobile>
by default, a desktop user might see a "flash" of <Mobile>
before a media query is resolved to show the <Desktop>
layout.
Inversely, if I set my default behavior to display <Desktop>
, and a mobile user visits, they will see a "flash" of <Desktop>
before the <Mobile>
layout is triggered.
There is no in between state here. Instead I would suggest wrapping both layouts in a loading message.
<MediaQuery Media="@Breakpoints.SmallDown" @bind-Matches="IsSmall" />
@if(!isLoading) {
@if(IsSmall){
<div>small</small>
}else{
<div>large</div>
}
} else {
<span>Loading</span>
}
@code {
bool IsSmall;
bool isLoading = true;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender) isLoading = false;
}
}
My opinion:
true - matched false - not matched unknown - loading. The media query is technically still false as it does not match.
yes, it's loading state, but your solution of problem is very unreliable. index component:
@page "/"
<h1>Hello, world!</h1>
Welcome to your new app.
<SurveyPrompt Title="How is Blazor working for you?" />
<Component1></Component1>
<Component2></Component2>
Component1:
<MediaQueryList>
@using BlazorPro.BlazorSize
@using System.Diagnostics
<MediaQuery Media="@Breakpoints.SmallUp" @bind-Matches="IsLarge" />
<h3>Component1</h3>
@if (!isLoading)
{
@if (_isLarge)
{
<div>large</div>
}
else
{
<div>small</div>
}
}
else
{
<span>Loading</span>
}
</MediaQueryList>
@code
{
bool _isLarge = false;
Stopwatch stopwatch;
private bool isLoading;
public bool IsLarge
{
get => _isLarge;
set
{
Console.WriteLine($"{stopwatch.ElapsedMilliseconds}, IsLarge changed from {_isLarge} to {value}");
_isLarge = value;
}
}
protected override void OnInitialized()
{
stopwatch = Stopwatch.StartNew();
}
protected override void OnAfterRender(bool firstRender)
{
if (firstRender) isLoading = false;
Console.WriteLine($"{stopwatch.ElapsedMilliseconds}, firstRender: {firstRender}, IsLarge:{_isLarge}");
}
}
Component2:
<h3>Component2</h3>
data: @data
@code {
string data;
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
//imitation of loading data
await Task.Delay(10);
data = "loaded";
}
}
ping: 500ms (for clarity) Console output (from 77ms to 1099ms loaded is true, but component is not loaded yet):
77, firstRender: True, IsLarge:False
1099, IsLarge changed from False to True
1102, firstRender: False, IsLarge:True
Component1 will be "small" after data loading in Component2 and before MediaQuery response. It may be any change in any part of application, including server push change (normal practice for server-side blazor).
Note: This still needs proper documentation.
This issue isn't as much about "nullable" as it is the lifecycle of a Blazor component and the availability of JavaScript.
The solution below works, even with Network latency. The reason I suggest this method is because I would use this same solution internally if I were to add support for Nullable. Rather than add a breaking change to BlazorSize by supporting nullable, in addition adding a new responsibility to the tool (detecting loading), I would rather those who need this approach implement their own loading pattern in their application.
If two components (per above) should load together, than the two components need to communicate their loading states to their parent, or the parent needs to dictate when loading has happened.
In the code below the component initializes with isLoading = true
. Once OnAfterRender
has been called, isLoading becomes false and the page is re-rendered with showing the loaded component.
If data is loaded during the OnInitializedAsync method, then BOTH the data and isLoading criteria should be met before showing the content.
<MediaQuery Media="@Breakpoints.SmallUp" @bind-Matches="IsLarge" />
<h3>Component1</h3>
@if (!isLoading)
{
@if (IsLarge)
{
<div>large</div>
}
else
{
<div class="alert alert-danger">You should never see this on desktop (small)</div>
}
}
else
{
<span>Loading</span>
}
@code
{
private bool isLoading = true;
public bool IsLarge { get; set; }
protected override void OnAfterRender(bool firstRender)
{
if (firstRender) isLoading = false;
}
}
For patterns and practices on loading states please refer to [BlazorPro .Spinkit](https://github.com/EdCharbeneau/BlazorPro.Spinkit) or [Telerik UI for Blazor](https://demos.telerik.com/blazor-ui/loadercontainer/overview)
@proff there's another way to accomplish exactly what you asked for with the MathchedChanged event.
@page "/nullable"
@if (IsSmall.HasValue)
{
if (IsSmall.Value)
{
<h1>Small</h1>
}
else
{
<h1>Large</h1>
}
} else
{
<span>Waiting for JavaScript interop...</span>
}
<MediaQuery Media="@Breakpoints.SmallDown" MatchesChanged="@(matched => IsSmall = matched)"></MediaQuery>
@code {
bool? IsSmall = null;
}
And this way exactly same as my workaround from first comment.
Indeed it is, I apologize for being daft.
MediaQuery match is async operation. Because of this when i use on small screen
then i see how "large" changed to "small". May be in client-side mode this is not noticeable, but in server-side noticeable.
When Matches will be nullable, i can use:
and will be "small"at once.
Workaround: