Example React app from Road to React translated to PureScript with react-basic-hooks

I’ve recently been reading through the book Road to React in order to learn more about React.

The first half of the book develops an example React app in JavaScript. Every chapter adds more features to the app and shows the reader more of the functionality of React.

Road to React was recently rewritten to be based on React Hooks.

I’ve translated the example from Road to React to PureScript using the purescript-react-basic-hooks library. If I was writing a larger application and not just a self-contained example, there are some things I would do differently, but I thought some people here might be interested anyway:

See the README.md in the repo for more info.


The translation from the JavaScript version to PureScript was very straight-forward (mostly thanks to how well react-basic-hooks is written), but there was one thing I had some trouble with.

I wasn’t sure how to represent JavaScript’s ability to have default values for function arguments.

For example, take the following component in JavaScript:

You can see that this component takes some props as arguments. The prop type has a default value of "text".

This is what I wasn’t sure how to express in PureScript.

I ended up using a type class to express a record with both optional and required values. I defined the defaults for the optional values in the function:

The InputProps type class synonym is defined as so:

This isn’t the prettiest code, but it seems to work.

How do other people deal with this type of situation?

3 Likes

One other thing that I was never able to figure out is how to get literal text embedded inside a React component.

For example, take the following code:

I want the   to render as an actual non-breaking space, but it just renders as the string  :

image

How can I fix this?

3 Likes

If I’m understanding the situation correctly, I think this is what @thomashoneyman used the FFI for in RealWorldHalogen:

Check out RawHTML.purs (and corresponding RawHTML.js) in this folder:

(I have no idea if there’s a more direct way with React Basic Hooks)

1 Like

Yea, Halogen escapes HTML and I’m assuming React Basic does too. There may be a way to insert a raw HTML string, though — @spicydonuts?

For setting raw HTML, the DOM elements expose React’s dangerouslySetInnerHTML. You can also use useRef and useEffect to access to the underlying DOM element instances if you need to do some advanced fiddling (most often needed when using a library, like d3, that wants to take ownership of the subtree at some point).


Optional vs required props is a bigger topic… the Union tricks work pretty well when the implementation is handled in JS. It’s used a bunch in the DOM library, to make all the props optional. To make a prop required you’d just need to move it to the function argument instead of the 3rd Union arg:

div
  :: forall attrs attrs_
   . Union attrs attrs_ ( thisPropIsOptional :: String )
  => ReactComponent { thisPropIsRequired :: String | attrs }
div = unsafeJSDiv

The problem with this is you can’t interact with the props object using PureScript without writing some unsafe code, so it doesn’t work for writing PureScript component implementations. You could maybe get around this using Record.merge and some unsafe coercing, or merging in FFI.

The easiest way to allow optional props entirely in PureScript is to build a default props record in your component and make your “props” a function:

component "OptionalProps" \mapProps -> React.do
  let props = mapProps defaultProps

You can then use the component like optionalProps _ { only = "set the ones", you = "need" }. We use this pattern in lumi-components. A wrapper around component enforces this “define defaults, map props” pattern (as well as some conventions for Emotion). Plus a couple aliases for convenience.

One issue this can introduce is that not all props have good or even possible default values. For these we end up using Maybe in the prop types fairly often. These have to be handled in the component implementation, but we can usually hide them from the external API. This prop is a maybe, but this helper wraps it for us. Using it looks like this:

button
  $ ariaLabel "Close"
  $$$ [ closeIcon ]

($$$ is a similar helper for _ { children = _ })

This pattern is fairly new for us but I like it a lot so far. Some parts of it could probably be pulled out into their own library as it stablizes, but it’s also all convention, so it could be used for a single component. It’s also not lost on me that this is essentially button [ ariaLabel "Close" ] [ closeIcon ] for many use cases… :sweat_smile:

Edit: I forgot to mention there are other libraries that try to address this more generally than “react components”, like purescript-options, which might be worth a look. Also note that in the hooks library, reactComponent requires the props to be a record, but component does not, which can be useful for cases like this.

4 Likes

Regarding encoding of optional fields I want to also add to the Madeline’s list of libs @jvliwanag’s purescript-untagged-union and (caution - a shamless plug) my simplified version of it purescript-undefined-is-not-a-problem.

3 Likes

If you need an example. I use dangerouslySetInnerHTML in my purescript-react-realworld:

4 Likes

@and-pete @Jonas @spicydonuts Thanks for the suggestion of dangerouslySetInnerHTML.

I was able to use this successfully:

3 Likes

@spicydonuts @paluh Thanks for the in-depth explanation of handling objects with optional values in PureScript.

I’ll have to take a closer look at some of your suggestions and see what makes the most sense for my code-base.