Reputation: 1493
I am using Blazor with Radzen Components. I have a page setup with two tabs. The first tab lists items that can be edited. The second tab references a component used to edit the selected item.
The code under the Edit button:
private TestVS selectedSchedule = new();
private async void EditVessel(VesselListItem vessel)
{
selectedSchedule.VesselLoadId = vessel.VesselLoadId;
await selectedSchedule.LoadVessel();
tabsSelectedIndex = 1;
}
When TestVS is created, the OnitializedAsync() method is executed:
protected override async Task OnInitializedAsync()
{
try
{
if (CurrentUserIsValid)
{
// Build drop down lists
var vList = await vesselData.GetVesselLoadList();
if (vList is not null)
{
vesselList = vList.ToList();
}
agentList = await vesselData.GetVesselAgentList();
destinationList = await vesselData.GetVesselDestinationList();
shipperList = await shipperData.GetList();
customerList = await customerData.GetList();
}
else
{
try
{
navigation.NavigateTo("/");
}
catch { }
}
}
catch (Exception ex)
{
LogError(ex);
}
}
This simply loads data for dropdowns on the form. The LoadVessel() method simply makes one more call to the DB to retrieve information:
public async Task LoadVessel()
{
try
{
if (VesselLoadId > 0)
{
vesselLoad = await vesselData.GetVesselLoadById(VesselLoadId);
}
}
catch (Exception ex)
{
LogError(ex);
}
// This throws a Null Reference Exception
StateHasChanged();
}
The [...]Data objects are injected and used for database operations. This represents a standard call to get data:
public async Task<VesselLoad?> GetVesselLoadById(int id)
{
var itemList = await _sql.LoadData<VesselLoad, dynamic>("dbo.spVesselLoad_GetById", new { VesselLoadId = id });
if (itemList is not null)
{
return itemList.FirstOrDefault<VesselLoad>();
}
return null;
}
I use this approach throughout this application and haven't had any problems until now. I've tried creating another method call Refresh() in TestVS that simply lets me call StateHasChanged() after the LoadVessel method is complete, but it still throws the null reference exception.
I can provide more code snippets if necessary. A much more complicated setup used to work fine for this page, but has now started failing inexplicably. If anyone has a clue, I could sure use one.
Upvotes: 0
Views: 76
Reputation: 1493
I found the problem. I recently switched a security object from a singleton to scoped. A collection inside that object is, of course, now NULL whereas before it was not. The system never lies, it's just not good at telling you the full details of a problem sometimes.
Once I loaded the collection, everything works fine. I apologize for my horrible example code that should have shown the full markup. That way, some of you would have surely noticed the possibility of a problem.
Thanks, MrC, you taught me something today.
Upvotes: 0
Reputation: 30375
Your problem is that you are creating and then using a component outside the context of the Renderer. You're getting an exception because the component's RenderHandle
is almost certainly null. It's assigned when the Renderer calls Attach
to attach the component to the Render Tree.
Some observations:
You should never new up a component like this [It's the reason for the exception].
private TestVS selectedSchedule = new();
If you want to add a reference to a component then, it should be nullable:
private TestVS? selectedSchedule;
You shouldn't rely on something like this in normal component design to force the component to update.
await selectedSchedule.LoadVessel();
Calling StateHasChanged
in normal component logic is an indicator that your component logic isn't quite right.
Based on the code you've provided, I've created a simple Minimal Reproducible Example (MRE). I may be missing some of the context, but I think this should be enough to get you started.
In the code below I use a standard Parameter and detected the change in the OnParametersSetAsync method.
Everything works within the component lifecycle and standard event handling. There's no need for extraneous calls to StatHasChanged
SelectedForm.razor
<h3>SelectedForm</h3>
<div>Vessel ID: @_vesselId </div>
@code {
[Parameter, EditorRequired] public int VesselId { get; set; }
private int _vesselId;
protected async override Task OnInitializedAsync()
{
// load stuff that doesn't change like dropdown lists
await Task.Yield();
}
protected async override Task OnParametersSetAsync()
{
// Do something if vessel id is 0.
// Detect a VesselId change and
// Update stuff
if(VesselId != _vesselId)
{
// Fake an async data get
await Task.Yield();
_vesselId = VesselId;
}
}
}
Home.razor
@page "/"
<PageTitle>Home</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
<div class="p-2"> Value: @_vesselId</div>
<RadzenTabs @bind-SelectedIndex=@_selectedIndex>
<Tabs>
<RadzenTabsItem Text="Selector">
<RadzenButton Click="() => EditVessel(1)" Text="Primary" ButtonStyle="ButtonStyle.Primary" />
</RadzenTabsItem>
<RadzenTabsItem Text="Editor">
<SelectedForm VesselId="_vesselId" />
</RadzenTabsItem>
</Tabs>
</RadzenTabs>
@code {
private int _selectedIndex = 0;
private int _vesselId;
private void EditVessel(int vessel)
{
// Dummy in an incrementing value
_vesselId = _vesselId + 1;
_selectedIndex = 1;
}
}
Upvotes: 1