ProductiveRage/Bridge.React

Using external components not in retyped?

Closed this issue · 15 comments

I use a small component library I found for formatting input fields called React Number Format:

https://github.com/s-yadav/react-number-format

It works really well and has Typescript support, but the library is not available on Retyped. How difficult is it to build the bindings for a library like that to use with Bridge.React?

On a related note I also use React Modal and React Select both of which are on Retyped. When I import those a Nuget packages into my solution, they pull in dependencies on Retyped.react and Retyped.dom. Isn't Retyped.react redundant since I am already pulling in Bridge.React and Bridge.Html5? Is there a problem when mixing and matching stuff like this within a Bridge.React application?

Have you looked through the Using third party / non-Bridge.NET components section of the Bridge.React README or the Using External JavaScript Bridge.NET wiki article? They should offer some pointers as to how to tackle writing your own bindings. Once you learn a few conventions, I would expect it to be quite straight forward.

Regarding the ReTyped bindings for React components.. ReTyped bindings are all automatically generated - this means that they can often be a good starting point and sometimes they can be good enough to use (depending upon what the TypeScript bindings look like - if they use a lot of structures that are not easily represented in C# then there will be more workarounds and "alien-looking" structures). There has been a question on the Bridge forums in the past about whether Bridge.React or retyped.react would be better and the Bridge Team suggested that Bridge.React is a more natural API as it is hand-written to follow C# conventions and includes convenient helper methods: https://forums.bridge.net/forum/community/retyped/4988-retyped-react-example. I also think (though I'm obviously biased!) that Bridge.React would be the better choice but it does mean that if you use retyped bindings for specific React components then you might have to be a bit creative with how you make them work together. You would need a way to tell the C# code that elements created by the React component libraries that you are communicating with via the retyped bindings are safe to be considered as instances of Bridge.React's "ReactElement". You should be able to write a method that does this, similar to the static Photo.Render method shown in the Using third party / non-Bridge.NET components information.

I have looked through the docs on building your own bindings. This is probably the biggest stumbling block to using Bridge.net in production code, is the need to build and maintain such bindings to third party projects (much like your Bridge.React bindings). Everybody does Typescript bindings these days, but it will be a decent amount of work to build and maintain C# bindings. Looking at the Retyped stuff, I am not sure that's a clean approach either from the sound of it, so maybe using Retyped bindings is not a good idea either and I should build my own C# bindings? Or for small projects like NumberFormat, maybe just port it all to C# in my own code base and maintain it there.

Does your Bridge.React binding library support newer features like React Router and React Context out of the box, or are those things still pending?

React Router isn't a feature of React, it's a separate library and would be a separate bindings package. I have an alternative called Bridge.ReactRouting but you'd have to evaluate that separately. I don't support Context because the docs recommend against its use and it wouldn't fit nicely into a type safe environment.

Ok great! I like the idea of a native library for routing anyway, so I will surely give that a look when I start to wire up some routing.

As for context, I think I will just continue to do what I have been doing already and passing my own context down through the components as a single Api property. It works fine and can also be made nice and type safe.

Ok I am working on a binding library for NumberFormat, and the Typescript types show that the props are derived from InputAttributes, but for some reason in Bridge.React InputAttributes is a sealed class?

https://github.com/kendallb/react-number-format/blob/master/typings/number_format.d.ts

Is there a reason why the InputAttributes binding in Bridge.React is sealed? It would be nice to be able to extend this class similar to how it is done in the Typescript bindings, but I am not sure if there is a good reason why the classes are left as sealed?

https://github.com/ProductiveRage/Bridge.React/blob/master/Bridge.React/Attributes/InputAttributes.cs

Also some parts in the type defs I am stuck on are these. Any idea how to convert those to C#?

    customInput?: React.ComponentType<any>;
    renderText?: (formattedValue: string) => React.ReactNode;
    getInputRef?: ((el: HTMLInputElement) => void) | React.Ref<any>;

When I am done maybe I should make a NuGet package from this?

Still stuck on customInput (but we don't use it is not a big deal at the moment). Not sure how I can accept a component since the components do not have a base class, but given the implicit operator method in your sample showing how to map things, I think maybe it should be just this?

			public ReactElement CustomInput { private get; set; }

The other two I think map to this for reference:

			public Action<ReactElement> RenderText { get; set; }
			public Action<HTMLElement> GetInputRef { get; set; }

I am also still stuck on the fact that InputAttributes is a sealed class, so we cannot derive from it like you can do in Typescript (and is done by the NumberFormat class itself). This is discussed in issue #28 and the argument is that it would allow you to pass down attributes to the input element that are not relevant, and that makes sense. In JSX components the normal way to handle this is to strip out your props and pass in just the left overs to the input element but we don't have an easy way to do that in C# using Bridge (no spread syntax).

So without the ability to derive from InputAttributes, the best I can think of at the moment is to just copy all the ones we think will be used into my properties class? This is far from ideal as when future properties get added to InputAttributes, I would have to clone them over for them to work. This is a great example of why InputAttributes should be unsealed, so I can extend from it?

Got any other suggestions on how to do this without InputAttributes being unsealed?

Pretty close to done with my wrapper. Conceptually I have a question about how to connect with the specific version of the component my wrapper is compiled against. It's possible some developers will be packing bundles of all their components and dependencies using Webpack or Browserify, but that brings in a bunch of extra tooling to a project. For a pure Bridge.Net project using Bridge.React it seems that is not entirely necessary and perhaps a better solution is to do something similar to what you have done with Bridge.ReactLoader, and include the script that the projects is built against as a resource, and probably version the package on NuGet with the same version as the script you have bundled with it? Then the resulting scripts can be packaged up using a regular ASP.net bundler like System.Web.Optimization.

Of course dependency tracking gets kinda nuts which is why people use tools like Webpack for large Javascript and Typescript projects. However with Retyped they seem to be following a similar approach of having the dependencies tracked via NuGet as well.

So maybe long term, a better approach would be to make binding libraries and publish to NuGet for the packages that we use, and include the scripts in the packages as resources. Then we can use them and manage dependencies like we do with normal C# libraries (of course the ordering they show up in the bundle is also important, which Webpack handles and would need to be handled manually if bundling using System.Web.Optimization).

If you plan on an entirely Bridge solution then I think that nuget packages with js resources included (like React Loader, as you point out) would work well. Hopefully dependency tracking will work in the same way the nuget already handles dependencies.

I think that ordering will be fine with bridge's analysis - it works out what projects / packages rely on which others and orders them appropriately in the output - either by including a list of script tags in the appropriate order or by generating one large js file if you have "combineScripts" set to true in your bridge.json file. Bridge's capabilities here should mean that you don't need to consider layering another bundler system on top.

Oh right! I totally missed that part about Bridge in that if the scripts are included as a resource, they end up being pulled into the distribution JS file. So I think you are right that it would work out the dependencies correctly so I will try that for my current component wrapper and see how it works.

Ugh, the issue with the script ordering is the only way Bridge does that is it outputs them in the correct order into the generated HTML file, which won't play nice with our existing MVC app framework. So I think I will still have to manually handle the dependencies myself.

One problem with Bridge.ReactLoader is that if I use that to bring the scripts over, the version of React is included in the script. If it is going to be auto generated into the distribution folder, perhaps it should be unversioned so it is easier to hook into the existing bundling code? Otherwise we have to update the bundling code any time we update the version of React via the loader.

Did you understand what I said about using "combineScripts"? That will generate a single .js file for your project, including all dependencies and so arranging them in the correct order in the Bridge-generated HTML file is not the only way that Bridge does this.

Yes, I know you can do that and I would likely do that for production builds, but for development builds it slows down the entire build process as it has to bundle up all the dependencies like the React and Bridge libraries. So for dev, I prefer to have them all separate rather than bundle them all into one file.

I guess it comes down to how much you want to pay to avoid that "combineScripts" slow down. If you want to set up another system to manage the dependencies then you could do that. But your earlier comment said that you had to (since "the only way Bridge does that is it outputs them in the correct order into the generated HTML file"), which wasn't true. It's good to have options, you can pick whichever compromises you find most palatable.

Yes, it is true I don't have to do it that way. Manually including the scripts solves that. Using something like webpack is likely to be just as slow as bundling to all one output file anyway so it's a preference thing.