0
votes

I have a component that I set a reference of it in a page variable:

<BlazorWebFormsComponents.Button OnClick="@((args) => btnForms_Clicked(formsButton, args))" @ref="formsButton" Text="Forms Button" CssClass="btn btn-primary">
    
</BlazorWebFormsComponents.Button>

In the event handler I Set a button property (Text):

Button formsButton;

public void btnForms_Clicked(object sender, MouseEventArgs e)
{                        
    if (sender is Button)
        (sender as Button).Text = "Good Bye";
}

For most of the Button properties this code is not working, For BackColor works but for Text not. Also blazor makes the assignment line, a green Underlined and says "Component parameter "zzz" should not be set outside of its component", So why Blazor provides a @Ref while most of the referenced properties can not be set? or is there a way to make this work?

3
I cast the object to Button and then use its properties. - mz1378
I have posted two pieces of code one for Markup and another for Code behind, The second Block of code that I posted is the code behind. - mz1378

3 Answers

0
votes

I'm not an export on Blazor, but ran into this as well. You can do this by binding to a property on the page.

<BlazorWebFormsComponents.Button OnClick="@((args) => btnForms_Clicked(formsButton, args))" @ref="formsButton" Text="@ButtonText" CssClass="btn btn-primary">

</BlazorWebFormsComponents.Button>

@code{
    private string ButtonText { get; set; } = "Forms button";

    public void btnForms_Clicked(object sender, MouseEventArgs e)
    {
        ButtonText = "Good bye";
    }
}

I have never used the @Ref and don't yet know how or when to use it.

0
votes

The warning is there because setting [Parameter] properties from code can cause the render tree to get out of sync, and cause double rendering of the component.

If you need to set something from code, you can expose a public method e.g. SetText on the component class, which does that for you.

Internally, the component [Parameter] should reference a local variable.

string _text;
[Parameter] 
public string Text { get => _text; set => SetText(value);}
public void SetText(string value)
{
  _text = value;
}

I am not promoting this approach, I prefer to use the approach in @Pidon's answer. Additionally, you could consider - maybe you have too many parameters and should consider an Options parameter to consolidate

0
votes
<Button OnClick="@((args) => btnForms_Clicked(formsButton, args))" @ref="formsButton" Text="Forms Button" CssClass="btn btn-primary">

Your Button component should be defined as follows:

@code
{
    [Parameter]
    public EventCallback<MouseEventArgs> OnClick {get; set;} 
    [Parameter]
    public string Text {get; set;} 

}

The above code define two parameter properties that should be assigned from the Parent component. The parent component is the component in which the Button component is instantiated. Note that you should set the above properties from the parent component as attribute properties... you must not set them outside of the component instantiation. Right now it's a warning, but Steve Anderson has already sad that it is going to be a compiler error soon. This is how you instantiate your component in the parent component:

Parent.razor

<Button OnClick="@((args) => btnForms_Clicked(args))" @ref="formsButton" 
      Text="_text" CssClass="btn btn-primary">
    
</Button>

@code
{
private Button formsButton;

// Define a local variable wich is bound to the Text parameter
private string _text = "Click me now...";

public void btnForms_Clicked( MouseEventArgs e)
{                        
    _text = "You're a good clicker.";
}
}

Note: When you click on the Button component a click event should be raised in the Button component, and the button component should propagate this event to the parent component; that is to execute the btnForms_Clicked method on the parent component, Here's how you do that:

Button.razor

<div @onclick="InvokeOnClick">@Text</div>

@code
    {
        [Parameter]
        public EventCallback<MouseEventArgs> OnClick {get; set;} 
        [Parameter]
        public string Text {get; set;} 

        private async Task InvokeOnClick ()
        {
             await OnClick.InvokeAsync(); 
        }
    
    } 

Note that in order to demonstrate how to raise an event in the Button component, and propagate it to the parent component, I'm using a div element, but you can use a button element, etc.

@onclick is a compiler directive instructing to create an EventCallback 'delegate' whose value is the event handler InvokeOnClick. Now, whenever you click on the div element, the click event is raised, and the event handler InvokeOnClick is called... from this event we execute the EventCallback 'delegate' OnClick; in other words, we call the btnForms_Clicked method defined in the parent component.

So what the @ref directive is good for? You may use the @ref directive to get a reference to a component that contain a method you want to call from its parent component: Suppose you define a child component that serves as a dialog widget, and this component define a Show method, that when is called, display the dialog widget. This is fine and legitimate, but never try to change or set parameter properties outside of the component instantiation.