[SOLVED] `react-basic-hooks` hello world?

I am confused. I’m seeing lots of “porting guides” and talks about someone using Purescript + React somewhere, so it’d seem like… well… it’s used. But I found exactly 0 (zero) working “Hello World”-level tutorials. Furthest I saw so far was a code that just compiles. Doesn’t work in a browser though, it’s just buildable.

For your enjoyment I made a quick showdown of all “Hello World” React PureScript tutorials I managed to get a hang of, further below.

With that said, my question is simple: can please somebody provide a simple react-hooks (as I presume “react-classic” is nowadays ain’t that interesting) code that when I do:

  1. spago bundle-app
  2. firefox ./index.html (where index.html is obviously pre-bundled by me and just refers to index.js that got created by 1)

I get a “Hello World” label in my browser?


Here’s showdown of all “Hello World” tutorials I managed to find. Besides tutorial-specific problems, I also found that in general “blog-post tutorials” overcomplicate everything. They try to introduce a web-server (which not only unnecessary but also goes south as you’ll further see), and frequently also npm-related components and js even though you already have spago with react, and everything else would seem unnecessary.

  • Official react-basic page: it’s messy. If you go through everything, you’ll find in different locations 3 non-404ed references:

    1. Examples in purescript-cookbook. I tried ButtonReactHooks one, which sounds simple enough. Well… it builds, that all I can say. If you use spago bundle-app to create index.js you get Could not resolve "./output/Main/index.js". I found a pre-bundled index.js file, apparently I wasn’t the first to stumble upon the error… And it goes along with pre-bundled index.html, but opening it just shows empty page :man_shrugging:
    2. purescript-react-realworld: that’s obviously not a “Hello World”.
    3. Examples in react-basic v14.0.0 branch. I tried counter which sounds simple, but it fails to even compile with Unable to parse module: Unexpected token 'RowList'.
  • Getting started with a PureScript React project | by Zelenya: the tutorial flow is random, it introduces npm start before introducing styles.css in a separate chapter, so npm start produces errors; and later it introduces PureScript code before installing react-player in a separate chapter, so again you get errors.

    But even if done correctly, all I can say is that the code builds. Running parcel serve … via npm start results in malloc(): invalid size (unsorted), and pointing a browser directly to public/index.html just shows an empty page.

  • The no-nonsense PureScript and React HelloWorld tutorial: first problem is npm install --save-dev craco-purescript-loader gives error unable to resolve dependency tree. Solved with --legacy-peer-deps. Barring that, with everything in place npm run start gives compilation error Webpack has been initialized using a configuration object that does not match the API schema.

After having dug into cookbook recipes I found a solution. While on it, it’s interesting to note that there’s a bug somewhere, because while digging I found that spago build wasn’t actually succeeding for me in recipes. It was kind of “pretending” to succeed, because it literally prints “Build succeeded.”, but above that was a path-related error.

Anyway, on to the solution.

The code is the one from the recipe. For this to work you need to install not just purescript package, but also node ones via NPM.

$ spago install effect exceptions maybe prelude react-basic react-basic-dom react-basic-hooks web-dom web-html
$ npm install react react-dom

So, the purescript:

module Main where

import Prelude

import Data.Maybe (Maybe(..))
import Effect (Effect)
import Effect.Exception (throw)
import React.Basic.DOM as R
import React.Basic.DOM.Client (createRoot, renderRoot)
import React.Basic.Events (handler_)
import React.Basic.Hooks (Component, component, useState, (/\))
import React.Basic.Hooks as React
import Web.DOM.NonElementParentNode (getElementById)
import Web.HTML (window)
import Web.HTML.HTMLDocument (toNonElementParentNode)
import Web.HTML.Window (document)

main :: Effect Unit
main = do
  doc <- document =<< window
  root <- getElementById "root" $ toNonElementParentNode doc
  case root of
    Nothing -> throw "Could not find root."
    Just container -> do
      reactRoot <- createRoot container
      app <- mkApp
      renderRoot reactRoot (app {})

mkApp :: Component {}
mkApp =
  component "Buttons" \_ -> React.do
    count /\ setCount <- useState 0
    let handleClick = handler_ <<< setCount
    pure $ R.div_
      [ R.button { onClick: handleClick (_ - 1), children: [ R.text "-" ] }
      , R.div_ [ R.text (show count) ]
      , R.button { onClick: handleClick (_ + 1), children: [ R.text "+" ] }
      ]

…and index.html. Note that the HTML requires a div-wrapper with an id root.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Halogen App</title>
  <link rel="stylesheet" href="styles.css">
</head>
<body>
  <div id="root"></div>
  <script src="index.js"></script>
</body>
</html>

After executing spago bundle-app and opening index.html in a browser you shall see the buttons.

The code begs a question: why does it use unnecessary “root” div wrapper, can’t it just use body tag?

Yes, it can. In fact, I did modify this code to get rid of root in preference of body, but running it in a browser results in the following print, which presumably implies the div isn’t so “unnecessary”:

Warning: createRoot(): Creating roots directly with document.body is discouraged, since its children are often manipulated by third-party scripts and browser extensions. This may lead to subtle reconciliation issues. Try using a container element created for your app.