[Updated]: How to replace React components using PureScript's React libraries

I ran into this issue too when launching a react project that bypassed create-react-app (so no webpack setup automatically). To follow-up on the previous post, here’s a parcel command that resolves this require error:

parcel serve assets/index.html -o index--parcelified.html --open

This assumes assets/index.html points to the js script generated from your purescript code.

@thomashoneyman Is your article hosted somewhere that takes PRs? I noticed some inconsistencies when I went through the tutorial. Also, is the final result tracked? Linking to something like @ptrfrncsmrph’s react-hooks port would be a helpful fallback reference.

@milesfrain My writing is on GitHub but in a private repository but I really ought to move the writing out to its own repository which can accept pull requests. I simply haven’t gotten around to it. And that’s a good idea to link to the react-hooks port, doing that now (Edit: updated on the site).

In the meantime, feel free to post article edits here or to send them to me (my email is on my site at https://thomashoneyman.com/open-invitation).

Thanks for the link! This was the first thing I attempted in PureScript, so for my part I’ve tried to polish it up with a few things I’ve learned since, but I’ve really only dabbled in the language. If there is anything in there that should be corrected, please let me know!

Seems good to me, though I usually inline render functions like renderCounter. Typing out the hooks type can be tedious and usually isn’t necessary.

1 Like

I edited the initial post in this thread to reflect that this article has been updated to use react-basic-hooks. It also describes some new conveniences with create-react-app from @andys8 for bootstrapping hybrid PureScript / JavaScript projects. If you haven’t read the original or you’re interested in a refresher the post is updated now!

4 Likes

What is this Data.Interpolate magic?! :exploding_head:

Should these names be reversed?

+mkCounter :: Props -> JSX
+mkCounter = unsafePerformEffect counter
+
 counter :: Component Props
+import { mkCounter as Counter } from "./MyApp/Components/Counter.purs";
1 Like

It feels more natural to me to import counter when using this in PureScript code and for something like mkCounter to exist when exporting this via the FFI. But I’m open to changing my mind if I’m missing something here! I’ve only toyed with react-basic-hooks as we use the low-level bindings to react at work.

1 Like

Nate came up with this idea here: Feature request: String interpolation

It’s a neat trick where instance chains recursively act like Semigroup appends over the first argument.

class TCName a where
  functionName :: SemigroupLikeType -> a

instance first :: TCName SemigroupLikeType where
  functionName :: SemigroupLikeType -> SemigroupLikeType
  functionName value = value
else instance second :: TCName a => TCName (SemigroupLikeType -> a) where
  functionName :: SemigroupLikeType -> (SemigroupLikeType -> a)
  functionName firstSemi = 
    \secondSemi -> functionName (firstSemi <> secondSemi)
else instance third :: (OtherConstraintsNeeded b, TCName a) => TCName (b -> a) where
  functionName :: SemigroupLikeType -> (b -> a)
  functionName firstSemi = 
    \b -> functionName (funcFromOtherConstraints firstSemi b)
1 Like

Ah, ok. It’s very much up to you :slight_smile: I just started using that convention when the exports started becoming effects instead of the components themselves. That way they’d get imported as mkX instead of x, then used as x <- mkX as the parent component is constructed, and its eventual use in the render function would remain x. We also sometimes continue to export components using unsafePerformEffect do component ... and in those cases continue using the x name as the export.

I missed that transition (when making components became effectful) – does that mean that you now need to do this when using them? (In that case my article is actually a bit incorrect and I should make an update :slight_smile: )

component = ...
  child1 <- mkChild1
  child2 <- mkChild2
  
  pure $ div_ [ child1, child2 ]

It’s usually like this: Root.purs (though this uses a custom monad instead of Effect, same thing though)

-hooks has always used Effect, but -classic and -compat never have, which can result in unexpected bugs when those older component creation functions aren’t used at the module level (unexpected remounts/rerenders, dropped component state, etc). Halogen uses slots and static typing to avoid this I believe, but internally React uses function instance reference equality (plus a key prop if it exists`) to determine component identity. Since JS isn’t statically typed there’s no safe way to reuse the old component state or props if the component function changes.

1 Like

Oh, React. Interestingly, I haven’t experienced those issues with the low-level bindngs, but I don’t believe we ever define more than one component per module or anything like that.

I’ll go ahead and update the names so that mkCounter is the default and perhaps jsCounter is the export (which can then be moved to the interop module).

I’m also (at @milesfrain’s suggestion) going to be moving the site content out into a dedicated repository where others can make suggestions to update the content for things like this, if you have more suggestions afterwards.

You will if you try to parameterize a component with a type class.

1 Like

Same, we started trying to make generic form components using typeclasses and found they would remount (clearing the form and DOM state) on every render :frowning:

1 Like

@thomashoneyman Section 3 (installing craco) - you also need to install the craco-purescript-loader. Thanks for updating this article! It’s nice to come back and work through it again.

2 Likes

Thanks for catching this! I’ve updated that – and I’ve also made my writing available on GitHub if anyone happens to notice other issues! Happy to make further updates to this article so that it represents PureScript and react-basic-hooks correctly. @spicydonuts I’ve also updated the article to change the use of mkCounter.

4 Likes

This is a great article, thank you @thomashoneyman !

I’m about to embark on a giant TypeScript-to-PureScript conversion project. My own experience, the experience of my team, and your very compelling article has convinced me that this is a good idea and it’ll work.

The only thing I’m slightly apprehensive about is the react-redux in the TypeScript code. I’ve never worked with redux before.

If I’m reading this correctly, you were referencing an article about Sharing more complex information like Redux stores here? I can’t find that article, does it exist?

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

More generally, do you have any advice on how to deal with react-redux in a PureScript conversion project?

1 Like

Taking over an entire app with PureScript is quite a different beast. The challenges vary depending on the architecture of the existing app. I had to do this with my current project, which started as an inherited mess of TypeScript, React and Redux…

We had been expressly told not to “rewrite from scratch” but 3 months into development, and while adding new features, there was no TypeScript or Redux left. So it wasn’t from scratch, but it was a rewrite.

1 Like

Alas, I should never post my writing plans, as my ambition outpaces my available time. I haven’t written the other articles (yet). I probably won’t end up getting to the Halogen one now that it’s been years since I’ve done this, but I could still write about sharing a Redux store between JS React and PureScript React.

It is possible to share a Redux store and middleware, but as @robertdp mentioned, this is more involved than sharing components would be especially if your Redux setup is hairy. I do hope to come back to this topic, perhaps over this winter break, but I can’t promise I’ll have the time.

@natefaubion originally wrote the code to deal with Redux when converting our app, so he may have advice to share in this thread. I can hopefully circle back to this thread when I have some time to share my understanding as well. Sorry I don’t have more for you right now!

3 Likes