C
C#•7mo ago
Lundeful

What is the equivalent of this react component if made using C# and Blazor?

I'm used to just doing .NET + React, but since they improved Blazor so much with .net 8 I'm having some fun there, but there's one thing I haven't figured out yet. How to wrap existing components/create reusable ones in an easy user friendly way. With React you can easily wrap components like the example below, pass down and do whatever you want with the props, and then just spread the rest onto them. And by directly using the correct interface (or extending it to create extra props) you get full auto complete/intellisense for the props. Here is a simple example where you can create a styled input where the user can use it just like they would the underlying component. It would also be easy to modify this to add other props or change the behaviour. In here I add some basic styling which can be overwritten by the calling component if needed, but they also can use all the existing attributes for the input tag.
import { FC, InputHTMLAttributes } from "react";

export const CustomInput: FC<InputHTMLAttributes<HTMLInputElement>> = (className, ...props) => {
const baseClass = "custom-input";
const combinedClasses = `${baseClass} ${className || ""}`.trim();

return <input className={combinedClasses} {...props} />;
};
import { FC, InputHTMLAttributes } from "react";

export const CustomInput: FC<InputHTMLAttributes<HTMLInputElement>> = (className, ...props) => {
const baseClass = "custom-input";
const combinedClasses = `${baseClass} ${className || ""}`.trim();

return <input className={combinedClasses} {...props} />;
};
I've tried a few things. Wrapping it and using @ChildContent is easy, but then you're not really creating a new input, you're creating something around it unlike the react version. Ideally I would want to use it like this, where I can use the field just as if it was a regular <InputText> field, but my custom component could then set some defaults or modify the different inputs.
<InputText @bind-Value="myTextValue" class="my-class" />
<CustomInputText @bind-Value="myTextValue" class="my-class" />
<InputText @bind-Value="myTextValue" class="my-class" />
<CustomInputText @bind-Value="myTextValue" class="my-class" />
Is this possible or am I just too influenced by the JS/React way of solving things and I should be looking at it entirely differently? 😄
9 Replies
mg
mg•7mo ago
mg
mg•7mo ago
This could be helpful for regular HTML attributes But for passing component parameters through, I'm not sure
Lundeful
LundefulOP•7mo ago
I see that the components, such as InputText, are just classes that I can inherit and perhaps edit and override what I need from there. Seems like I can do this just using code and not bother with the html syntax at all. Seems a lot easier and more maintainable. I'll have to experiment, but seems like it will be easier than I feared 😄 Back to reading the source code
mg
mg•7mo ago
Ah, right. regular old inheritance
Lundeful
LundefulOP•7mo ago
@mg Got it working in a simple example here. Also using tailwind, which is where I think this can be a huge win. I'm basically just checking to see if the class is there, kinda wonky since it can be null Kinda wonky since the attributes is a nullable readonly dictionary and there isn't exactly an upsert for dictionary. That part can just be taken out as a utility function/extension method or something and then it can look quite clean in usage Definition
C#
public class MyInputText : InputText
{
private const string BaseClass = "mx-24";

protected override void BuildRenderTree(RenderTreeBuilder builder)
{
var attributes = AdditionalAttributes?.ToDictionary() ?? new Dictionary<string, object>();
if (attributes.TryGetValue("class", out var existingClass))
{
attributes["class"] = $"{BaseClass} {existingClass}";
}
else
{
attributes.Add("class", BaseClass);
}

AdditionalAttributes = attributes;
base.BuildRenderTree(builder);
}
}
C#
public class MyInputText : InputText
{
private const string BaseClass = "mx-24";

protected override void BuildRenderTree(RenderTreeBuilder builder)
{
var attributes = AdditionalAttributes?.ToDictionary() ?? new Dictionary<string, object>();
if (attributes.TryGetValue("class", out var existingClass))
{
attributes["class"] = $"{BaseClass} {existingClass}";
}
else
{
attributes.Add("class", BaseClass);
}

AdditionalAttributes = attributes;
base.BuildRenderTree(builder);
}
}
Usage
<MyInputText @bind-Value="_formModel.MyString" class="mx-64 my-12" />
<MyInputText @bind-Value="_formModel.MyString" class="mx-64 my-12" />
And it works fine by overriding the base value as I do here. Only problem is this does not work well with MudBlazor since all of their utility clases have !important on them 💀 The specificity got weird it seems like. With Tailwind you can use the @apply directive to reuse styles, but this method can do more than just template the styles
Lundeful
LundefulOP•7mo ago
Extracted it to a utility class and put the project into a public repo so it doesn't get lost https://github.com/Lundeful/reusable-blazor-components Hopefully we'll get a better solution for this soon
GitHub
GitHub - Lundeful/reusable-blazor-components
Contribute to Lundeful/reusable-blazor-components development by creating an account on GitHub.
SleepWellPupper
SleepWellPupper•7mo ago
GitHub
RhoMicro.ApplicationFramework/Presentation.Views.Blazor/Abstraction...
Contains helper types & structure for creating web or desktop Blazor (Photino) Applications. - PaulBraetz/RhoMicro.ApplicationFramework
Lundeful
LundefulOP•7mo ago
Cool stuff! Looks like you've been going down that rabbithole a lot more than me 😄 Does sort of feel like we're working against the framework when doing this, but if it works it works.
SleepWellPupper
SleepWellPupper•7mo ago
Comes with the territory of representing all attributes/parameters as KVP<String, Object?> but what can you do :catshrug: I just wrote today a nice type that encapsulates the html class names issue rather neatly, no more string checking operations, just Attributes["class"] = HtmlClassNames.Create(Attributes["class"]).Add("new-class-to-add").Remove("first-to-remove", "second-to-remove") I can share if you're interested
Want results from more Discord servers?
Add your server