Jsoo approach to react / gui

What is the jsoo approach to React ?

For example, JS/TS have builtin “syntax support” via JS → JSX, TS → TSX.

For jsoo, what is the approach to building GUI components ?

  1. jsoo with react, no syntax
  2. jsoo with react, special syntax (how do we extend?)
  3. jsoo without react (what library do we use? incr / functional-relational-programming related libs?)
1 Like

There’s a set of React bindings to JSOO written by @davesnx and @jchavarri, which you can find here: GitHub - ml-in-barcelona/jsoo-react: js_of_ocaml bindings for ReactJS. Based on ReasonReact.

The library includes a ppx which transforms the syntax slightly, but it doesn’t give you JSX or anything like that.

Here’s an example of how a component is written with the DSL + the PPX: jsoo-react-realworld-example-app/header.ml at main · jchavarri/jsoo-react-realworld-example-app · GitHub

(that repo broadly is an attempt to port the ReScript example app to jsoo-react. Not sure how completed it is, though)

2 Likes

You might find these previous threads interesting:

1 Like

Thanks for the mention @zbaylin.

We made jsoo-react with OCaml syntax in mind, but If you look for JSX, the inmediate solution is to go for Reason (and keep the components in Reason syntax)

1 Like

I am only here to worsen your dilemma.

Like me, you may feel less frustrated by not using JSOO barebones. Instead, use either Brr or gen_js_api bindings to JSOO.

Coupling that with the libraries that use The Elm Architecture (TEA), which had also inspired React (and is a much more complete concept than React is), there are quite a few paths for you:

Based on JSOO:

  • jsoo + gen_js_api + ocaml-vdom (TEA)
  • “jsoo + Brr + Note (which uses FRP instead of TEA or React’s philosophy)”
  • jsoo + gen_js_api + jsoo-react
  • jsoo + (incr_dom or bonsai)

I found the documentation and usage of Brr much simpler than those of gen_js_api. @dbuenzli clarifies what Brr is further down in this thread.

Not based on JSOO

  • melange + bucklescript-tea
  • melange + reason-react(?)
  • melange + bindings to whichever JS library you can fathom
3 Likes

Putting my two cents: I was able to achieve satisfactory developer experience using react in pure ML syntax in a toy project. Although the experiment used the ML syntax of ReScript instead of JSOO, you should be able to achieve the same result with a bit of extra works regarding FFI.

I built simple wrapper functions around react element constructors (“function components” in react jargons) mapping properties to labeled arguments and children to the only positional argument as a list (except those elements that never take children, e.g. <br/>, which take a single unit as the positional argument). This approach would allow you to build the virtual DOM in the following way:

(** e.g. [address_display ["Pine St. 12"; "ABC Bld."]] would render an equivalent of
    [<div style="font-size: 12">
       <span>Address</span><br/>
       <span key="line-0">Pine St. 12</span><br/>
       <span key="line-1">ABC Bld.</span><br/>
     </div>] *)
let address_display ?(font_size=12) lines =
  div ~style:S.["fontSize", font_size] ([
     span [ text_node "Address:" ];
     br();
   ] @ lines |> List.mapi (fun i line ->
        fragment [
          span ~key:(sprintf "line-%d" i) [ text_node line ]);
          br();
        ]
  )

which I actually liked better than TSX/JSX.

2 Likes

This was likely not your intent but I’d just like to mention that this seem a bit to imply exclusive use of these components.

So I’d just mention that Brr is simply bindings to a selection of browser APIs (the Note stuff in there is accidental and will migrate elsewhere at some point, likely as an depopt of Note).

gen_js_api is a library to generate bindings to JavaScript libraries.

Both can be used in the same project, including one that uses incr_dom, bonsai or ocaml-vdom.

This was likely not your intent but I’d just like to mention that this seem a bit to imply exclusive use of these components.

True. I tailored my answer to what the OP asked for.

I bungled the following:

jsoo + Brr (which does not use TEA though) + Note

What I meant was:

“jsoo + Brr + Note (which does not use TEA or React philosophy)”


gen_js_api is a library to generate bindings to JavaScript libraries.

Doesn’t the Brr FFI provide a similar capability? Probably through a more manual effort though.

gen_js_api generates bindings based on annoted .mli files. Brr does not generate bindings it provides an FFI API built on top of the bare-bone js_of_ocaml one.

Personally I’m not very fond of the gen_js_api approach. You need to learn a new language (the annotations), understand how that language translates to FFI calls and then there’s always stuff that escapes the provided patterns, so you may end up fighting with square pegs and round holes (e.g. to express the kind of horrible bureaucracy you find in the constrainable pattern)

Creating bindings with Brr entails a bit more manual work but in the end it’s more flexible to express your binding API and for the programmer it’s just a new OCaml API to learn.

In my experience what took (a lot of) time (and absinthe) was not writing the bindings per se but rather researching, presenting and documenting the JavaScript APIs so that they are enjoyable and productive to use in OCaml.

9 Likes

the Note stuff in there is accidental and will migrate elsewhere at some point, likely as an depopt of Note

Elm before 0.17 revolved around FRP and signals. news/farewell-to-frp

A few (relatively) recent JavaScript libraries like SolidJS also are based on signals (= values that change over time). https://www.solidjs.com/tutorial/introduction_signals

I have yet to explore, but in case you already know it, are these similar to Note in philosophy?

Note is an implementation of FRP yes.

I don’t exactly know what these recent JavaScript libraries do, but after all these years I’m glad they are finally discovering flapjax :–)

2 Likes

For the sake of discussion, I’ll point out that Angular has FRP “baked in” via the included rxjs library, see this link: Angular

@jayeshbhoot, the SolidJS intro you linked to looks very similar to Angular idioms.

It’s an interesting concept, however I wasn’t really satisfied with this paradigm (as is applied in Angular), mainly because one can introduce very subtle and hard to debug bugs, quite easily.

Here’s a minimal example demonstrating a “reactive” data flow. Hopefully it should be self explanatory.

// example.component.ts
import {Component, OnDestroy} from "@angular/core";
import {FormControl, FormGroup} from "@angular/forms";
import {combineLatest, Observable, of, Subject, Subscription, timer} from "rxjs";
import {User, UserService} from "@src/app/services/user.service";
import {catchError, debounceTime, map, startWith, switchMap, tap} from "rxjs/operators";

@Component({
  template: `
      <h1>Search example</h1>

      <form class="search-form" [formGroup]="searchForm" autocomplete="off">
          <div>
              <label for="firstName">First name</label>
              <input id="firstName" type="text" formControlName="firstName">
          </div>
          <div>
              <label for="lastName">Last name</label>
              <input id="lastName" type="text" formControlName="lastName">
          </div>
          <button type="button" (click)="refresh$.next()">Get fresh data</button>
      </form>

      <div>
          <h2>Search result</h2>
          <table [class.loading]="loading">
              <thead>
              <tr>
                  <th>First name</th>
                  <th>Last name</th>
              </tr>
              </thead>
              <tbody>
              <tr *ngFor="let user of users$ | async">
                  <td>
                      {{user.firstName}}
                  </td>
                  <td>
                      {{user.lastName }}
                  </td>
              </tr>
              </tbody>
          </table>
      </div>
  `,
})
export class ExampleComponent implements OnDestroy {
  searchForm = new FormGroup({
    firstName: new FormControl(),
    lastName: new FormControl(),
  });
  loading = false;

  refresh$ = new Subject();

  userInput$ = combineLatest([
    this.searchForm.valueChanges.pipe(debounceTime(200)),
    this.refresh$.pipe(
      startWith(null), // initiate data flow
    ),
  ]);

  private refreshSub: Subscription;
  userTotal = 0;

  users$: Observable<User[]> = this.userInput$.pipe(
    tap(_ => this.loading = true),
    switchMap(([params, _refresh]) => {
      return this.userService.search(params).pipe(
        map(({users, total}) => {
          this.userTotal = total;
          this.loading = false;
          return users;
        }),
        catchError((err) => {
          console.log('Oops, something went wrong!');
          return of([] as User[]);
        }),
      );
    }),
  )
  constructor(
    private userService: UserService,
  ) {
    this.refreshSub = timer(0, 60_000).subscribe(() => {
      this.refresh$.next();
    });
  }

  ngOnDestroy(): void {
    this.refreshSub.unsubscribe();
  }
}

All that to say that my prior exposure to “FRP” via Angular doesn’t really help me understand the “React” lib (nor the “Note” library for that matter)

Also worth mentioning “rxjs marbles” which are meant to help the programmer “visualize” the application of reactive functions (those functions are named “operators”), see here: RxMarbles: Interactive diagrams of Rx Observables

Crazy to see that “old” flapjax library language! :exploding_head:

The thing is Rx doesn’t do FRP. Its focuses more on events than on signals/behaviours.

From ReactiveX - Intro :

ReactiveX may be functional, and it may be reactive, but “functional reactive programming” is a different animal. One main point of difference is that functional reactive programming operates on values that change continuously over time, while ReactiveX operates on discrete values that are emitted over time.

I think even this explanation is off, because FRP makes use of both signals/behaviours and events.

There is an analogy somewhere when I say that Elm similarly lost something from FRP when it moved to its MVU/Elm architecture.

Do take all of what I say with a grain of salt, simply because I’m still learning the ropes. Anyone more experienced is welcome to tear down my statements.

What was once learned, has been learned again.