Discovered re-frame. Me like.

March 21, 2015 -- 3 minutes & 36 second read

I'm starting a new project at work so I'm doing some research on how people are structuring their ClojureScript + React.js apps these days. The architecture I came up with six months ago has served us well up until now. It was based around having no external DB, so data was put into the app code as fixtures and then written into IndexedDB during startup. It served our needs but it doesn't scale well at all. There has been a recent explosion in the amount of data we are serving with our largest file reaching 3 MB. We are now moving everything into Datomic and reevaluating all aspects of the app.

I've been spending time thinking about the re-frame pattern. It's the first time I've read anything on FRP (functional reactive programming) and so far I quite like it. I have a little experience working with Samza and writing stream processors for it with Clojure. Beyond that I don't know much about functional concurrency using reactive programming. It's really neat to work with values that evolve over time as first class values. Taking a stream of input and filtering, mapping, and reducing it to create data sequences that serve your needs is pretty powerful if used correctly.

The app I'm building doesn't really have that requirement. We ingest a lot of data from our content API, with most of it being pre-fetched during startup. If content isn't available when it's needed then the system simply skips over displaying it and moves on to the next available content. Our current tech stack is a Clojure HTTP Kit server, and a ClojureScript client that uses Reagent for rendering.

The biggest mistake I made when designing the first version of the app is having some state in multiple places. I was coming from an OOP background and didn't feel comfortable using one big global atom as the app's DB. I now see the benefits to this approach. If you keep the state in a secluded area and restrict write access to all but one component then you may be able to sleep soundly at night.

The re-frame pattern uses a single atom for all state. Nothing stops you from using local state in a component but you need to be careful when it comes to component-to-component communication. I would use the event bus when a component needs to work with another and they aren't in a parent-child hierarchy. For components in a hierarchy, it's OK for a parent to pass data to children via parameters.

Communication in re-frame happens over an event bus. You simply dispatch events and write handlers for said events. The handlers are pure functions that receive the app DB value at that point in time. It simply needs to perform its work and return a new version of the DB. Due to how reagent works, the DB mutation may trigger a re-render of some or all components.

A Reagent component re-renders itself if its template makes use of an ratom (Reagent atom) and that atom is modified. With re-frame you aren't binding a component directly to the app-db atom. Instead you write query functions that fetch data from the app-db and setup Reagent reactions. I won't get into the reaction stuff now but you can read all the details in the re-frame readme. Anyways, you have a query now and a component that wants that data. You make use of the query's returned value in the component and when that value changes the component re-renders.

Overall this approach is FRP mixed with some FSM. I'm going to prototype some code with it this weekend and hopefully I will have a plan to migrate my existing app over to some form of this pattern.

If you're interested, I started a thread on the ClojureScript mailing list which has yielded some interesting and insightful comments on how people are using re-frame and similar patterns.

Tags: reagent clojurescript