leSyndrome
leSyndrome

Reputation: 23

How to preserve component instances in Blazor

I have a similar component and I whish to save the instance of the InnerComponent the first time it is rendered and render the same instance every time without reinstanceiating it.

@if(isVisible)
{
    <InnerComponent @ref="@_InnerComponent" @key="@("InnerComponentKey")">
        @ChildContent
    </InnerComponent>
}

@code{
    [Parameter] public InnerComponent _InnerComponent { get; set; }
    private bool IsVisible { get; set; }
}

When the inner component is visible the user can manipulate its state. But if IsVisible gets set to false and then true again, the inner component gets rerendered overriding _InnerComponent and thus we lose track of the changes that the user made to that InnerComponent instance.

Adding @key does not seem to help preserve the instance either. It just gets rerendered and overwritten :/ I am sure, that I am feeding it the same key both times it gets rendered, but I don't know how to inspect the keys that it gets compared to.

If it is possible to render a component instance I could do something like the following, but I can't seem to find a way to do that.

@if(isVisible)
{
    @if(_InnerComponent == null)
    {
        <InnerComponent @ref="@_InnerComponent" @key="@("InnerComponentKey")">
            @ChildContent
        </InnerComponent>
    }
    else
    {
        @_InnerComponent.Render()
    }
}

I am taking criticism on my question since I haven't asked many :)

Thanks in advance!

Simplified example:

Let's say we have the following component that I am going to call `CounterContainer` , where `<Counter>` is the counter component from the default Blazor project template.
@if(CounterIsVisible)
{
    <Counter @ref="@_Counter" @key="@("CounterKey")" />
}

<button @onclick="() => CounterIsVisible = !CounterIsVisible">
    Show/Hide counter 
</button>

@code{
    [Parameter] public Counter _Counter { get; set; }
    private bool CounterIsVisible { get; set; } = true;
}

I want to save the _Counter instance, so I can see the correct _Counter.currentCount that I counted to. I could save it using a method from this article, but I find them all unpractical, because

I already have the Counter reference stored. I just want to view it instead of reinstanceiating it and overwriting the whole thing.

Hope that made it a bit clearer (:

Upvotes: 1

Views: 2053

Answers (5)

SteveL
SteveL

Reputation: 97

What I did for that is use the html style attribute like this:

<InnerComponent style="@(IsVisible ? "display:block" : "display:none")"></InnerComponent>

When using @if Blazor add/remove the component from the DOM so when it's added back, it follow the initialization life cycle.

Upvotes: 1

Topher
Topher

Reputation: 1029

I've seen the option of hiding the component with CSS and the unexpected problems it can cause because components then aren't going through normal life cycle events when they are legitimately rerendered/reinstantiated. So I'm busy updating several components to fix that exact thing. My advice would be to avoid doing that if possible.

Another way, similar to BennyBoys carrying instance method, is you could probably do it is by utilizing dependency injection and registering a type as a scoped or singleton service and having it injected into the component.

You can read here on Chris Sainty's blog about how the different registrations work in blazor and their behaviour and if it'll fit your needs.

https://chrissainty.com/service-lifetimes-in-blazor/

I would double check via official docs that these still behave the same way though just in case but it might be a nice way to do it. I think it depends on what your actual use case is whether it's a good idea to do it like this, but it's another option :)

Upvotes: 0

Bennyboy1973
Bennyboy1973

Reputation: 4216

The way I like to do this kind of thing is to create a carrying class which I instantiate in the parent and pass to the child as a Parameter. Since a class is passed by reference, I don't need to worry about events or anything like that. (I just made up a class for demo to show you can have whatever you want in it)

CounterData.cs

public class CounterData
    {
        public int counter { get; set; }
        public string Title { get; set; } = "Counter";
        public List<int> CounterHistory { get; set; } = new List<int>();
    }

CounterContainer.razor

@page "/counter"

<h3>Counter Container</h3>

<button @onclick="()=> ShowCounter=!ShowCounter">Toggle</button>

@if (ShowCounter)
{
    <Counter CarryingInstance="PersistentData" />
}

@code {
    CounterData PersistentData = new CounterData();
    bool ShowCounter = false;
}

Counter.razor

<h3>@CarryingInstance.Title</h3>

<input @bind="CarryingInstance.Title" /><br />
<button @onclick="()=> Increment(1)">Add</button>
<button @onclick="()=>Increment(-1)">Sub</button>
@CarryingInstance.counter <br />


History:

@foreach (var item in CarryingInstance.CounterHistory)
{
    <span>&nbsp; @item</span>
}

@code {
    [Parameter]
    public CounterData CarryingInstance { get; set; }

    void Increment (int amount)
    {
        CarryingInstance.counter += amount;
        CarryingInstance.CounterHistory.Add(CarryingInstance.counter);
    }
}

Upvotes: 0

Henk Holterman
Henk Holterman

Reputation: 273464

  • best option: separate View and Data. The state you want to preserve should not be kept inside the component but in a separate (ViewModel) object.

  • short solution: always render the component. Use @isVisible to turn a css-class on/off to hide it.

Upvotes: 1

Bennyboy1973
Bennyboy1973

Reputation: 4216

I'm surprised it compiles, since you've misspelled your component class:

[Parameter] public **InnerCopmonent** _InnerComponent { get; set; }

Why do you have an @ref to your InnerComponent at all? What does InnerComponent do, and why do you want to reference it? Can you share that component's code?

Upvotes: 1

Related Questions