Skip to content

Commit 0bd997b

Browse files
authored
Surface warning/approach for component state (#17455)
1 parent dfc79b3 commit 0bd997b

2 files changed

Lines changed: 101 additions & 5 deletions

File tree

aspnetcore/blazor/components.md

Lines changed: 99 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ description: Learn how to create and use Razor components, including how to bind
55
monikerRange: '>= aspnetcore-3.1'
66
ms.author: riande
77
ms.custom: mvc
8-
ms.date: 03/25/2020
8+
ms.date: 04/21/2020
99
no-loc: [Blazor, SignalR]
1010
uid: blazor/components
1111
---
1212
# Create and use ASP.NET Core Razor components
1313

14-
By [Luke Latham](https://github.com/guardrex) and [Daniel Roth](https://github.com/danroth27)
14+
By [Luke Latham](https://github.com/guardrex), [Daniel Roth](https://github.com/danroth27), and [Tobias Bartsch](https://www.aveo-solutions.com/)
1515

1616
[View or download sample code](https://github.com/dotnet/AspNetCore.Docs/tree/master/aspnetcore/blazor/common/samples/) ([how to download](xref:index#how-to-download-a-sample))
1717

@@ -134,6 +134,9 @@ In the following example from the sample app, the `ParentComponent` sets the val
134134

135135
[!code-razor[](components/samples_snapshot/ParentComponent.razor?highlight=5-6)]
136136

137+
> [!WARNING]
138+
> Don't create components that write to their own *component parameters*, use a private field instead. For more information, see the [Don't create components that write to their own parameter properties](#dont-create-components-that-write-to-their-own-parameter-properties) section.
139+
137140
## Child content
138141

139142
Components can set the content of another component. The assigning component provides the content between the tags that specify the receiving component.
@@ -392,7 +395,7 @@ Consider the following example:
392395

393396
The contents of the `People` collection may change with inserted, deleted, or re-ordered entries. When the component rerenders, the `<DetailsEditor>` component may change to receive different `Details` parameter values. This may cause more complex rerendering than expected. In some cases, rerendering can lead to visible behavior differences, such as lost element focus.
394397

395-
The mapping process can be controlled with the `@key` directive attribute. `@key` causes the diffing algorithm to guarantee preservation of elements or components based on the key's value:
398+
The mapping process can be controlled with the [`@key`](xref:mvc/views/razor#key) directive attribute. `@key` causes the diffing algorithm to guarantee preservation of elements or components based on the key's value:
396399

397400
```csharp
398401
@foreach (var person in People)
@@ -446,6 +449,99 @@ Generally, it makes sense to supply one of the following kinds of value for `@ke
446449

447450
Ensure that values used for `@key` don't clash. If clashing values are detected within the same parent element, Blazor throws an exception because it can't deterministically map old elements or components to new elements or components. Only use distinct values, such as object instances or primary key values.
448451

452+
## Don't create components that write to their own parameter properties
453+
454+
Parameters are overwritten under the following conditions:
455+
456+
* A child component's content is rendered with a `RenderFragment`.
457+
* <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> is called in the parent component.
458+
459+
Parameters are reset because the parent component rerenders when <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> is called and new parameter values are supplied to the child component.
460+
461+
Consider the following `Expander` component that:
462+
463+
* Renders child content.
464+
* Toggles showing child content with a component parameter.
465+
466+
```razor
467+
<div @onclick="@Toggle">
468+
Toggle (Expanded = @Expanded)
469+
470+
@if (Expanded)
471+
{
472+
@ChildContent
473+
}
474+
</div>
475+
476+
@code {
477+
[Parameter]
478+
public bool Expanded { get; set; }
479+
480+
[Parameter]
481+
public RenderFragment ChildContent { get; set; }
482+
483+
private void Toggle()
484+
{
485+
Expanded = !Expanded;
486+
}
487+
}
488+
```
489+
490+
The `Expander` component is added to a parent component that may call `StateHasChanged`:
491+
492+
```razor
493+
<Expander Expanded="true">
494+
<h1>Hello, world!</h1>
495+
</Expander>
496+
497+
<Expander Expanded="true" />
498+
499+
<button @onclick="@(() => StateHasChanged())">
500+
Call StateHasChanged
501+
</button>
502+
```
503+
504+
Initially, the `Expander` components behave independently when their `Expanded` properties are toggled. The child components maintain their states as expected. When `StateHasChanged` is called in the parent, the `Expanded` parameter of the first child component is reset back to its initial value (`true`). The second `Expander` component's `Expanded` value isn't reset because no child content is rendered in the second component.
505+
506+
To maintain state in the preceding scenario, use a *private field* in the `Expander` component to maintain its toggled state.
507+
508+
The following `Expander` component:
509+
510+
* Accepts the `Expanded` component parameter value from the parent.
511+
* Assigns the component parameter value to a *private field* (`_expanded`) in the [OnInitialized event](xref:blazor/lifecycle#component-initialization-methods).
512+
* Uses the private field to maintain its internal toggle state.
513+
514+
```razor
515+
<div @onclick="@Toggle">
516+
Toggle (Expanded = @_expanded)
517+
518+
@if (_expanded)
519+
{
520+
@ChildContent
521+
}
522+
</div>
523+
524+
@code {
525+
[Parameter]
526+
public bool Expanded { get; set; }
527+
528+
[Parameter]
529+
public RenderFragment ChildContent { get; set; }
530+
531+
private bool _expanded;
532+
533+
protected override void OnInitialized()
534+
{
535+
_expanded = Expanded;
536+
}
537+
538+
private void Toggle()
539+
{
540+
_expanded = !_expanded;
541+
}
542+
}
543+
```
544+
449545
## Partial class support
450546

451547
Razor components are generated as partial classes. Razor components are authored using either of the following approaches:

aspnetcore/blazor/lifecycle.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ description: Learn how to use Razor component lifecycle methods in ASP.NET Core
55
monikerRange: '>= aspnetcore-3.1'
66
ms.author: riande
77
ms.custom: mvc
8-
ms.date: 03/17/2020
8+
ms.date: 04/16/2020
99
no-loc: [Blazor, SignalR]
1010
uid: blazor/lifecycle
1111
---
@@ -205,7 +205,7 @@ For information on handling errors during lifecycle method execution, see <xref:
205205

206206
## Stateful reconnection after prerendering
207207

208-
In a Blazor Server app when `RenderMode` is `ServerPrerendered`, the component is initially rendered statically as part of the page. Once the browser establishes a connection back to the server, the component is rendered *again*, and the component is now interactive. If the [OnInitialized{Async}](xref:blazor/lifecycle#component-initialization-methods) lifecycle method for initializing the component is present, the method is executed *twice*:
208+
In a Blazor Server app when `RenderMode` is `ServerPrerendered`, the component is initially rendered statically as part of the page. Once the browser establishes a connection back to the server, the component is rendered *again*, and the component is now interactive. If the [OnInitialized{Async}](#component-initialization-methods) lifecycle method for initializing the component is present, the method is executed *twice*:
209209

210210
* When the component is prerendered statically.
211211
* After the server connection has been established.

0 commit comments

Comments
 (0)