Is it possible/advisable to work from an existing DOM with Coconut?

This is my 1st week into both React and coconut. Followed Juraj’s calculator workshop for coconut, and a very simple React like button tutorial you can find at https://reactjs.org/docs/add-react-to-a-website.html.

The React tutorial puts several <p>Some comment <div class="like_button_container"></div></p> directly into an index.html file, and then they have a LikeButton class, and this code:

document.querySelectorAll('.like_button_container')
    .forEach( domContainer => {
        // Read the comments ID from a data-* attribute.
        const commentID = parseInt( domContainer.dataset.commentid, 10 );
        ReactDOM.render(
            e(LikeButton, { commentID: commentID }), 
            domContainer
        );
    });

The React example’s index.html is like:

<!doctype html>
<html lang="en">
<head></head>
<body>
  <h1>hello world</h1>
  <p> 
    Blabla Bla Jim, some little comment.
    <div class="like_button_container" data-commentid="1"></div>
  </p>
  
  <p> 
    Blabla Bla Dwight, some little comment.
    <div class="like_button_container" data-commentid="2"></div>
  </p>
  
  <p> 
    Blabla Bla Michael, some little comment.
    <div class="like_button_container" data-commentid="3"></div>
  </p>
  
  <script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
  <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
  <script src="like_button.js"></script>
</body>
</html>

Now I tried to convert that to coconut.
But because of the workshop I followed and the Haxe API (js.Browser.querySelectorAll() seems quite difficult to work with, with node types it seems hard to work with attributes) I chose the same approach as in the coconut workshop:

package;

import tink.state.*;
import tink.pure.*;
import coconut.Ui;
import coconut.ui.View;

class Main extends coconut.ui.View {
    static function main() {
        js.Browser.document
            .querySelector('#please-to-render-here')
            .appendChild(new Main({}).toElement()
        );
    }

    function render() '
  <div>
      <p>
        Blabla Bla Jim, some little comment.
        <Like data_commentid="1" />
      </p>

      <p>
        Blabla Bla Dwight, some little comment.
        <Like data_commentid="2" />
      </p>
    
      <p> 
        Blabla Bla Michael, some little comment.
        <Like data_commentid="3" />
      </p>
  </div>
  ';  

And

class Like extends View {
    @:attribute var data_commentid : String;
    @:state     var liked : Bool = false;

    function render() '
        <if {liked}>
            <i>comment nr. {data_commentid} was liked by you</i>
        <else>
            <button onclick={liked = true}>Like</button>
        </if>
    ';
}

It was surprisingly easy… But that’s not at all the same approach as in the React example I used.
This is more like no html, fully js-generated style.

Some questions to the coconut community:

  • How to use the same approach as in the React example, i.e. iterate through some DOM nodes to update that content? do we have to use the js.Browser and so on?
  • Is it less advisable, e.g. in Coconut is the second approach more … used?

I’m trying to get a feel for the different possible approaches.

The JS React example doesn’t assimilate the element obtained by querySelectorAll but renders children elements inside it.

Something like that should work in Haxe

for (node in js.Browser.document.querySelectorAll('.like_button_container')) {
    var elem: HtmlElement = cast node;
    elem.appendChild(new Like({ commentid: elem.dataset.commentid }).toElement());
}

If you want to avoid the nesting you could use replaceChild: Node.replaceChild() - Web APIs | MDN

In addition to what Philippe said, new SomeView(props).toElement() is no longer a thing. Where did you get that? ^^

The correct way is either coconut.Ui.hxx('<LikeButton commentID=$commentID />') or if you feel very strongly about not using hxx, then LikeButton.fromHxx({ commentID: commentID }), although this is not mean as a user facing API.

Instead of ReactDOM.render(vdom, targetElement), you would use coconut.ui.Renderer.mount(vdom, targetElement).

Thanks for the helpful answers.

I was going to say from the workshop, but turns out it’s from another example I also studied, tic-tac-toe from @kevinresol :slight_smile:

Because of all the necessary libs and lix scope and what not I made a bash coconut-init Script to create new coconut projects in seconds. The template is based on the tic-tac-toe from which I removed most code, but the new Main({}).toElement() comes from there.

Mystery solved :sweat_smile:. Thx again.

2 Likes