Anna Pawlicka

Programmer. Hiker. Cook. Always looking for interesting problems to solve.

About | Archive | Talks

Common mistakes to avoid when creating an Om component. Part 2.

02 Nov 2014 | clojure, ClojureScript

It’s been a while since the last post. More mistakes have been made, lessons have been learned, so here’s a handful:

  1. Combining cursors
Make sure you're updating the cursor and not the map that combines them.
Let's say you want to create a component that has a view of bands and titles of films. You don't want to pass whole of the app state,
you just want to pass `:albums` and `:titles`.
You combine them like this:

(def app-state {:music {:artists []
                        :bands {:faved []}
                        :albums {:faved []}}
               :films {:directors []
                       :titles {:faved []}}})

(om/build select-favourites {:bands  (-> cursor :music :bands)
                             :titles (-> cursor :films :titles)})

Inside of you `select-favourites` component you might assume that cursor is an actual cursor. It's not, it's just a map containing cursors. You can't do this:

(defn select-favourites [cursor owner]
(om/transact! cursor [:bands :faved] #(conj % some-band))))

You can't update the map. You need to get the cursor out of the map:

(defn select-favourites [{:keys [bands titles]} owner]
(om/transact! bands :faved #(conj % some-band))))
  1. Method signature and protocol implementation
Sometimes you may forget the protocol implementation or you may provide something that doesn't match the method signature.
If you see no meaningful errors coming from the compiler, upgrade ClojureScript.

(defn some-component [cursor owner]
    (render [_])))

ClojureScript compiler warns you nicely about the mistake:

WARNING: Bad method signature in protocol implementation, om/IRenderState does not declare method called render at line 159 src/cljs/pumpkin/core.cljs

I remember seeing `java.lang.IndexOutOfBoundsException` which was caused by missing protocol implementation. Upgrading helped.
Try to stay up to date with both Om and ClojureScript. You'll see more meaningful errors and warnings.
  1. Don’t do computations in render
If you need to group-by, or do other sorts of data transformations, it's a bad idea to do it in render as it will slow it down.
Try to transform your data as early as possible if the transformation is not caused by immediate user interaction.
I usually do it in `will-mount`.
  1. Don’t create mult channel in render
Let's say some parent component creates a number of components that depend on size of your data and you want all
children to receive messages broadcasted on a core.async channel. Create a mult of that channel in init-state. T
hen, when children components are built in render, create a copy of that mult channel using tap. If you create mult in render 
all sorts of weirdness will happen.
  1. Oh god, don’t use lazy sequences in cursors.
This one should be obvious, but if you map, remove or filter something in your data and update the cursor with the result,
you will end up with a lazy sequence. Remember about `mapv`, `filterv` or `into []`.

That’s all I remember. Hope it’s useful!

Older · View Archive (25)

Draggable wrapper component with Om and core.async

I’ve been looking for a way to enable dragging of Om components, something similar to what Draggable does but much much simpler. I just want to drag component around the UI, no bells and whistles. I didn’t want to add this functionality to each component but just enable it as needed. Hence a wrapping component.


My first Conj

Thanks to good people at Cognitect and the sponsors of the Opportunity Grant I had an opportunity to speak at Clojure/conj 2014. It was the second time I’ve given a talk, and a fourth tech conference I attended. And it’s been amazing!