Testing the re-frame Clojure frontend framework
Posted on 2022-01-16 in Programmation
I wanted to test re-frame a Clojure framework for building user interfaces. It is built on top of Reagent a Clojure library to interface with React in ClojureScript. The main goal of this framework is to help you manage the state of your application. To do that, you need to:
- Dispatch events.
- Handle these events.
- Handle the effect of the event, this means mutate the state to take into account the side effect calculated at the previous step. For the application state, re-frame will to it automatically for you, but you can have custom handlers if needed.
- Query the store to get the current state.
- Use this state in a view to display it.
Here are some notes on this experience. They are quite dense and you may need to look in the documentation to dig a bit deeper, mostly if you don't know Clojure or ClojureScript.
- You can use Clojure in the backend and ClojureScript in the frontend and share code in .cljc files (for Clojure common) allowing you to use the same language in both the frontend and the backend.
- You don't need many node library but you do need many Java/Clojure ones. They will be downloaded automatically for you.
- As always with Clojure, the startup is slow but all subsequent compilations are very fast. The page is also live reloaded correctly for each changes.
- I think re-frame is well design: once you get the gist of it (either from the documentation or the example supplied with the template), it's easy to modify and add features. The separation between views, events and subscription makes each part easy to grasp.
- Minified production code is about 400kB for my TODO app. It's 20 times bigger than my Svelte one and 2.5 times bigger than a base React app. So not too big but not small either. I guess re-frame adds overhead as well as the Clojure standard library. That's to be expected and shouldn't be such a big deal if it's worth it.
- You cannot simply use (js/console.log data) to inspect data from the store or an event: the objects are ClojureScript objects and contains many extra properties that makes them hard to read. You will need more powerful debugging tools fast.
- Errors can be a bit obscure (as often with Clojure). Those that can be displayed in the browser (in a nice "warning modal") are easy to find and fix. For all the other ones, you will have to rely on the source maps to find them.
- Since we are in Clojure, all data structure are immutables and you have good functions to manipulate and update them. That's a strong selling point if you go down the immutable road.
- I'm not convinced with the way you need to write your template code. It's fully integrated with Clojure: instead of using JSX which looks like actual HTML, you use a vector that contains each values, like this: [:div {:style {:color "red"}} [:p "Some text"] [my-component "A Prop"]]. I like that I can use Clojure structures instead for something else for this, but I don't find it very readable. It may just be a lack of habit though.
- CSS must be handled manually on the side, but you can add style directly to a node and do so dynamically.
- When you subscribe to the state, you get an atom and need to extract the current value from it with something like @todo. I think the @ is easy to forget and the error you get when you do is nothing from obvious.
To conclude: as always, I find it a bit hard to come back to Clojure after break. Since the syntax of the language is small, it's easy to read, but I always forget some functions or parameters order. Adding re-frame makes it harder to dive back, but overall it's structure is clean, powerful and easy to understand. So it wasn't a big deal. And I find it more explicit than Redux in how it behaves and better structured making it easier to grasp. It also can handle HTTP calls out of the box while pushing side effects to the boundaries of the program. Overall, the experience was pleasing although I'm still skeptic about the LIST syntax.
Here is my code: https://gitlab.com/Jenselme/test-todo/-/tree/main/reframe