[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.

1 Like

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. It requires NPM packages along with purescripts ones.

  1. create a repo with spago init

  2. Install packages with

    $ spago install effect exceptions maybe prelude react-basic react-basic-dom react-basic-hooks web-dom web-html
    $ npm install react react-dom
    
  3. Overwrite src/Main.purs with the following code:

    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 "+" ] }
          ]
    
  4. Create index.html as follows. Note: 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>
    
  5. Execute spago bundle-app and open 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.

1 Like