Working with DOM

Let's try working with the DOM in pure Reason before jumping into ReasonReact. We'll write a module that does the following:

  • Creates a DOM element
  • Sets the innerText of that element
  • Appends that element to the body of the document

Create an index.html file in the project's root with the following content:

<html>
<head></head>
<body>
<!-- if "in-source": false -->
<script type="module" src="lib/es6/src/Demo.bs.js"></script>

<!-- if "in-source": true -->
<!-- <script type="module" src="src/Demo.bs.js"></script> -->
</body>
</html>

Notice the type="module" attribute on the script tag. If all module dependencies are ES Module (ESM) compliant, and they are all available from within the browser, you don't need a module bundler to get started (assuming you're using a browser that supports ES modules).

In Greeting.re, add the following greeting function:

let greeting = name => {j|hello $name|j};

And in Demo.re, add the following code:

[@bs.val] [@bs.scope "document"]
external createElement : string => Dom.element = "";

[@bs.set] external setInnerText : (Dom.element, string) => unit = "innerText";

[@bs.val] [@bs.scope "document.body"]
external appendChild : Dom.element => Dom.element = "";

let div = createElement("div");
setInnerText(div, Greeting.greeting("world"));
appendChild(div);

Using BuckleScript's powerful interoperability features (which we will dive into in Chapter 4, BuckleScript, Belt and Interoperability) the preceding code binds to existing browser APIs, namely document.createElement, innerTextand document.body.appendChild, and then uses those bindings to create a div with some text that is appended to the body of the document.

Run npm run build, start a server (perhaps with php -S localhost:3000 in a new console tab) at the root of the project, and then navigate to http://localhost:3000 to see our newly-created DOM element:

The takeaway is that having to work with the DOM in this way is really tedious. It's hard to type DOM APIs due to JavaScript's dynamic nature. For example, Element.innerText is used both to get and set an element's innerText, depending on how it's used, which therefore would result in two different type signatures:

[@bs.get] external getInnerText: Dom.element => string = "innerText";
[@bs.set] external setInnerText : (Dom.element, string) => unit = "innerText";

Luckily, we have React, which largely abstracts the DOM for us. Using React, we don't need to worry about typing the DOM APIs. It's nice to know that when we want to interact with various browser APIs, BuckleScript has the tools we need to get the job done. While it's certainly possible to write frontend web applications in pure Reason, it's a much more pleasant experience when using ReasonReact, especially when first getting started with Reason.