Post: How to replace React components using PureScript's React libraries

react
#1

I have seen (and asked!) many questions about interop when introducing PureScript to an existing JavaScript application. I’ve now worked on rewriting an Angular app into Halogen and a React app into PureScript React and, with plenty of guidance from @natefaubion, learned a few best practices.

This post demonstrates how to take over a React component using PureScript. It’s meant for beginner-to-intermediate PureScript devs and omits no code along the way. It’s the first of three articles:

  1. Taking over individual components with PureScript React & React Basic
  2. Doing the same with Halogen
  3. Sharing more complex information like Redux stores

https://thomashoneyman.com/articles/replace-react-components-with-purescript/

If enjoyed the article & you know folks with a React codebase who are also interested in functional programming, consider sharing it with them! It’s my hope that these sorts of pieces can help teams feel more confident transitioning to PureScript with their existing applications.

11 Likes
#2

This is really cool, and thank you for writing it.

I have been trying to incorporate some PureScript code to an existing TypeScript project with mixed success (some weirdness on either side of the code.)

My base setup is similar. Sharing src folder, symlinking output into src etc. But now I’ll go and experiment with creating interop code similar to yours to reduce/eliminate weirdness.

I have a question – any recommendations on representing more complex sum types? Something like:

data Action
  = Increment
  | Decrement
  | SetTo Int

(Perhaps the answer will be in “part 3” :smiley:)

(BTW, I’m using react-basic-hooks, but had to fork it because the current official impementation fails “rules of Hooks” on the TS side. If folks are interested, I can share my solution.)

#3

For interop with TypeScript, purescript-variant works pretty well. @justinw has written quite a bit on the subject. https://github.com/justinwoo/purescript-ohyes

1 Like
#4

@mugatti Which implementation and rules are you referring to? What breaks?

#5

@natefaubion, thanks! I’ll take a look at purescript-variant. BTW, I have been influenced heavily by @justinw’s posts and libraries in this effort. I’m generating my TypeScript declarations for product types basically in a very similar way to his, but I was stuck with the sum types.

@spicydonuts, I was talking about this issue. My setup is very similar to @thomashoneyman’s where I’m using compiled PureScript output as an input to my TypeScript/React build. I couldn’t find a way to override/edit the linter rules without “ejecting” from create-react-app which I didn’t want to do, so I forked the library for my own usage, and named all the hooks.

Edit: this is the change I made: https://github.com/muratg/purescript-react-basic-hooks/commit/ff755c67e6ed963a9aaf0500a6a41917df663322

#6

It was a great article. Thank you! I went through all the examples, and to get the last one (i.e., Introducing More Complex Types) to compile, I had to change the following in Interop.purs:

import Counter (CounterType(…), Props, counter, counterTypeFromString)

becomes:

import Counter (CounterType(…), Props, component, initialState, render, counterTypeFromString)

Thus, I’m using the same imports and the same jsCounter code from the previous example (Making it usable from JavaScript).

2 Likes
#7

@adkelley Thanks for catching this – it’s fixed on the site now :slight_smile:

#8

@mugatti I’m glad you found it helpful!

With regards to the more complex sum types @natefaubion’s variant suggestion is a good one, as the output code is quite clear. For example, your data type, translated, might be:

type Action = Variant
  ( increment :: Unit
  , decrement :: Unit
  , setTo :: Int
  )

on the PureScript side; on the JavaScript side, your SetTo constructor would end up being an object like this:

{ type: "setTo", value: 10 }

which also lends itself to use on the JavaScript side with switch statements, like this:

processAction = action => {
  switch (action.type) {
    case "increment":
      ...
    
    case "setTo":
      console.log("count", action.value);
      ...
    
    default:
      console.log("didn't understand the action type")
  }

The next post in this series is about using Halogen components in JavaScript code bases and will get in-depth about doing this sort of thing to handle messages emitted by Halogen components. So you’ll have a bit more of a taste there!

Still, this isn’t a full answer, because you could be trying to represent all sorts of types in JavaScript. Some other approaches I’ve used include:

  1. Decide how I would have represented this in JavaScript and then translate backwards to PureScript – for example, maybe the component can take a string "increment" or "decrement", or an object with an action and a value like { "setTo": 10 }, and on the PureScript side I have to decode this. In that case I’d likely say that the JavaScript will provide a Foreign type with a comment and decode it manually (type Props = { counterType :: Foreign }).

  2. Decide that it’s not worth trying to make a good JavaScript representation of the type and instead export values directly for JavaScript to use. For example, I might export the values increment = Increment, decrement = Decrement, and setTo n = SetTo n and then import and use those values in JavaScript directly. Sometimes this is much easier than trying to do a translation.

I haven’t had to reach for anything more involved than what I’ve described here, fortunately, but I can imagine that others like @justinw have more advanced techniques.

1 Like
#9

Ah, I still don’t really understand that…

  1. It’s a linter rule, not a React rule (and technically the function is named correctly, just not in the style the linter wants)
  2. Linters should never run on generated code. No code generator could possibly make all linters happy with the same emitted code.
#10

Any interest in adding a react-basic-hooks section? I’ve found interop to be even easier since you use the same paradigm for both PS and JS components, and if you end up needing to write a stateful component in an FFI file the hooks api lets you skip JS classes altogether (classes are extra tedious without the ES2016 class keyword).

3 Likes
#11

I am interested in that, though I’ll have to learn about it first :slight_smile: I’d love to see how that works. Also I’m happy to link out to related articles if you’ve written anything on this topic!

1 Like
#12

I tried to port your tutorial to the hooks API here (and attempted a custom hook in Form.purs): https://github.com/ptrfrncsmrph/purescript-react-tutorial
Would be interested in feedback as I’m new to PureScript and there’s maybe some language features I’m not taking advantage of.

1 Like