ProductiveRage / Bridge.React

Bindings for Bridge.NET for React - write React applications in C#!
MIT License
74 stars 14 forks source link

Easy way to convert HTMLElement into a ReactElement #18

Closed badcommandorfilename closed 7 years ago

badcommandorfilename commented 7 years ago

Hi Dan,

Firstly, thanks for this amazing library - I'm using it for all my new projects.

I have come up with the following pattern to inject data into a React Component when working with Razor and static HTML pages:

        [Ready]
        public static void Go()
        {
            var containers = new Dictionary<string, Action<Element>>
            {
                ///Containers are top level components, pass Inner HTML to Props as input
                { typeof(SimpleModalContainer).Name, (e) => React.Render(new SimpleModalContainer(new SimpleModalContainer.Props { Inner = e }), e) },
            };

            //This will attach the React elements to the DOM body when the page loads
            foreach (var type in containers)
            {
                var elements = Document.GetElementsByTagName(type.Key);

                foreach (var element in elements)
                {
                    //Construct the React element (and pass in any InnerHTML if necessary)
                    type.Value(element);
                }
            }
        }

Which lets me write HTML elements with the same name as the React Component class and inject parameters into them as child elements:

            <SimpleModalContainer>
                <a open class="button is-primary is-large modal-button">Create New Thing</a>
                <div view id="confirm_create" class="modal">
                    <div class="modal-background"></div>
                    <div class="modal-card" style="width:60%">
                        <div class="notification is-warning">
                            <header class="modal-card-head">
                                <a close class="delete"></a>
                            </header>
                            <section class="modal-card-body">
                                @Html.Partial("Create", new Thing { ThingId = ViewBag.ThingId })
                            </section>
                        </div>
                    </div>

                    <a close class="modal-close" data-target="modal_close"></a>
                </div>
            </SimpleModalContainer>

This works well, but the issue I'm having is that the Children of the SimpleModalContainer will get overwritten when the Render() method runs.

What I do presently is read the "Inner" HTMLElements during ComponentWillMount(), and then parse the content into Props and recreate the elements as ReactElements, e.g.

        public override ReactElement Render()
        {
            return
...
                DOM.A(new AnchorAttributes
                {
                    ClassName = Props.OpenButton.Class,
                    OnClick = e => props.OpenButton.OnClick()
                },
                    props.OpenButton.Label
                );
        }

What I'm looking for is just a quicker way to convert an HTMLElement into a ReactElement or return an existing HTMLElement from Render. Is the pattern I described above a reasonable approach, or should I be using another strategy to inject parameters into my React components?

Thanks again and keep up the great work!

ProductiveRage commented 7 years ago

It sounds like you're trying to come up with a way to allow "isomorphic" (aka "universal") rendering, where the server and the client are both capable of generating content and any server-rendered content is then hooked up to React components and events in the browser. Which sounds like an interesting idea!

React does have some builtin facility to do this but it generally involves executing JavaScript on the server (which is fine if you had a Node back end but less convenient if you want to work with .NET).

For my purposes, I've not tried to look too deeply into server side rendering. I want to create React web applications that are run (and the UIs generated) entirely on the client. I'm not interested in having server-side content generated for either the perception of performance nor for search engines to have content to index (since I don't want search engines to index any content from my applications). What I'm saying is that I haven't done a lot of research into whether or not React server-side rendering is feasible with a .NET back end, so I might not actually be the best person to weigh in on this.

However, I do have some thoughts on what you've described.. and I'm worried that it might not be possible for the general case. The only way that I can imagine it working is if you parse the HTML delivered from the server and then try to enumerate through all of the elements and create React components for them (divs, sections, etc..). There are two big problems that I see with this idea, though. Firstly, I can't envisage how you would hook up event handlers - for example, from the parsed HTML, how would you know what code in your client library to call for a given button click? Secondly, unless you are including the names of custom components in the server-generated HTML then you will only be able to parse out basic element types (like div, input, p, etc..) and a huge benefit of React is the ability to create custom components. On the other hand, if you're including the names of custom components in the HTML (which, actually, your example markup seems to suggest) then you wouldn't be able to render the HTML in the browser without using React (and so there wouldn't be any server side HTML generated for search engine indexing and suchlike).

Have I missed the point, maybe? Are you aiming to use Razor as a sort of alternative to JSX?

ProductiveRage commented 7 years ago

I was thinking about this some more. If you're thinking about a way to more neatly write component markup in your project (rather than thinking about rendering content server side, which the client then hooks React up to) then you might want to consider using TSX. It's essentially a TypeScript version of JSX that is supported by a range of editors (including VS 2017, according to this). You could still write all of the logic for your client side application using Bridge but then create your components in TypeScript / TSX. Bridge has support for generating TypeScript definition for Bridge libraries, so consuming Bridge code from TypeScript components should be pretty easy.

Personally, I'm quite happy writing my component classes as "vanilla" C# code (eg. calling "DOM.Div" when I want to create a div) but I'm aware that a lot of people really appreciate a JSX-like style of component declaration. While I haven't tried the Bridge / TSX combination myself, it's something that I have thought before could be good for people who like the look and feel of JSX but want something a little more strongly typed and with better editor support for intellisense.

badcommandorfilename commented 7 years ago

Thanks for your suggestions Dan,

I'm trying to retrofit an existing MVC5 application (hence the mix of Razor and React). Basically, the views are mostly server-side generated, but the existing UI uses a mongrel jQuery solution to handle client-side events and there isn't a reliable RESTful API layer for data.

The result I'm looking for is a way to include static server-side HTML data as part of a React Component's props. The modal example above is a good one - the Component is a container which can show/hide some static HTML that was generated on the server (so I can re-use the same SimpleModalComponent on a bunch of different pages).

I've since found a way to achieve what I need (you can close this issue if you like), although it relies on dangerouslySetInnerHTML:

            return DOM.Div(new Attributes
            {
                Id = self.InnerID, //A handle for tracking the DOM element
                DangerouslySetInnerHTML = new RawHtml { Html = self.Inner.InnerHTML } //The initial HTMLElement from the Razor View
            });

Which leads me to think that maybe my whole approach is contrary to the "React Way".

I'll look into TSX as a replacement, but is DangerouslySetInnerHTML as dangerous as the name suggests? Is there a more "Reacty" way to inject model state into my Components?

Thanks again!

ProductiveRage commented 7 years ago

I can't think of a idiomatic way to do what you want to do - largely, I think, because you're attempting an unusual combination of technologies (or, at least, you're combining them in an unusual way).

The reason that it's "dangerous" is that React will not have knowledge of the HTML contents and so I don't think that it will be able to hook up event handlers and it wouldn't be able to perform efficient diff'ing when trying to work out what has or hasn't changed in the DOM when further changes are applied.