[logo](/images/logo/re-frame_128w.png?raw=true) ## Derived Values, Flowing > This, milord, is my family's axe. We have owned it for almost nine hundred years, see. Of course, sometimes it needed a new blade. And sometimes it has required a new handle, new designs on the metalwork, a little refreshing of the ornamentation ... but is this not the nine hundred-year-old axe of my family? And because it has changed gently over time, it is still a pretty good axe, y'know. Pretty good. > -- Terry Pratchett, The Fifth Elephant
>     Reflecting on identity, flow and derived values [![Clojars Project](https://img.shields.io/clojars/v/re-frame.svg)](https://clojars.org/re-frame) [![GitHub license](https://img.shields.io/github/license/Day8/re-frame.svg)](license.txt) [![Circle CI](https://circleci.com/gh/Day8/re-frame/tree/develop.svg?style=shield&circle-token=:circle-ci-badge-token)](https://circleci.com/gh/Day8/re-frame/tree/develop) [![Circle CI](https://circleci.com/gh/Day8/re-frame/tree/master.svg?style=shield&circle-token=:circle-ci-badge-token)](https://circleci.com/gh/Day8/re-frame/tree/master) ## Why Should You Care? Perhaps: 1. You want to develop an [SPA] in ClojureScript, and you are looking for a framework 2. You believe Facebook did something magnificent when it created React, and you are curious about the further implications. Is the combination of `reactive programming`, `functional programming` and `immutable data` going to **completely change everything**? And, if so, what would that look like in a language that embraces those paradigms? 3. You're taking a [Functional Design and Programming course at San Diego State University](http://www.eli.sdsu.edu/courses/fall15/cs696/index.html) and you have a re-frame/reagent assignment due. You've left the reading a bit late, right? I remember those days. Just. 4. re-frame is impressively buzzword compliant: it has reactivity, unidirectional data flow, pristinely pure functions, interceptors, coeffects, conveyor belts, statechart-friendliness (FSM) and claims an immaculate hammock conception. It also has a charming xkcd reference (soon) and a hilarious, insiders-joke T-shirt, ideal for conferences (in design). What could possibly go wrong? ## re-frame re-frame is a pattern for writing [SPAs] in ClojureScript, using [Reagent]. This repo contains both a **description of this pattern** and a **reference implementation**. McCoy might report "It's MVC, Jim, but not as we know it". And you would respond "McCoy, you damn trouble maker, why even mention an OO pattern? re-frame is a **functional framework**." Being a functional framework, it is about data, and the pure functions which transform that data. ### It is a loop Architecturally, re-frame implements "a perpetual loop". To build an app, you hang pure functions on certain parts of this loop, and re-frame looks after the `conveyance of data` around the loop, into and out of the transforming functions you provide - hence the tag line "Derived Data, Flowing". ### It does Physics Remember this diagram from school? The water cycle, right? Two distinct stages, involving water in different phases, being acted upon by different forces: gravity working one way, evaporation/convection the other. To understand re-frame, **imagine data flowing around that loop instead of water**. re-frame provides the "conveyance" of the data - the equivalent of gravity, evaporation and convection. You design what's flowing and then you hang functions off the loop at various points to compute the data's phase changes. Sure, right now, you're thinking "lazy sod - make a proper Computer Science-y diagram". But, no. Joe Armstrong says "don't break the laws of physics" - I'm sure you've seen the videos - and if he says to do something, you do it (unless Rich Hickey disagrees, and says to do something else). So, this diagram, apart from being a plausible analogy which might help you to understand re-frame, is **practically proof** it does physics. ### It is a 6-domino cascade Computationally, each iteration of the loop involves a 6 domino cascade. One domino triggers the next, which triggers the next, etc, until we are back at the beginning of the loop. Each iteration is the same cascade. ### 1st Domino An `event` is sent when something happens - the user clicks a button, or a websocket receives a new message. Without the impulse of a triggering `event`, no 6 domino cascade occurs. It is only because of `events` that a re-frame app is propelled, loop iteration after loop iteration, from one state to the next. re-frame is `event` driven. ### 2nd Domino In response to an `event`, an application must compute the implication (the ambition, the intent). This is known as `event handling`. Event handler functions compute `effects`. Or, more accurately, they compute a **description of `effects`**, which means they say, declaratively, how the world should change (because of the event). Much of the time, only the state of the SPA itself need change, but sometimes the outside world must too must be effected (localstore, cookies, databases, emails, logs, etc). ### 3rd Domino These descriptions of `effects` are actioned. The intent is realised. Now, to a functional programmer, `effects` are scary in a [xenomorph kind of way](https://www.google.com.au/search?q=xenomorph). Nothing messes with functional purity quite like the need for effects and coeffects. But, on the other hand, `effects` are equally marvelous because they take the app forward. Without them, an app stays stuck in one state forever, never achieving anything. So re-frame embraces the protagonist nature of `effects` - the entire, unruly zoo of them - but it does so in a controlled, debuggable, auditable, mockable, plugable way. ### Then what happens? So, that 3rd domino just changed the world and, very often, that involves one particular part of the world, namely the **app's state**. re-frame `app state` is held in one place - think of it like you would an in-memory, central database for the app. When domino 3 changes `app state`, it triggers the next part of the cascade involving dominoes 4-5-6. ### The view formula The 4-5-6 domino cascade implements the formula made famous by Facebook's ground-breaking React library: `v = f(s)` A view `v` is a function `f` of the app state `s`. Or, said another way, there are functions `f` which compute what DOM nodes, `v`, should be displayed to the user when the application is in a given app state, `s`. Or, another way: **over time**, as `s` changes, `f` will be called each time to compute new `v`, forever keeping `v` up to date with the current `s`. Now, in our case, it is domino 3 which changes `s`, the application state, and, in response, dominoes 4-5-6 are about re-running `f` to compute the new `v` shown to the user. Except, of course, there's nuance. For instance, there's no single `f` to run. There may be many functions which collectively build the overall DOM, and only part of `s` may change at any one time, so only part of the `v` (DOM) need be re-computed and updated. And some parts of `v` might not even be showing right now. ### Domino 4 Domino 4 is a novel and efficient de-duplicated signal graph which runs query functions on the app state, `s`, efficiently computing reactive, multi-layered, "materialised views" of `s`. (Relax about any unfamiliar terminology, you'll soon see how simple the code actually is) ### Domino 5 Domino 5 is one or more **view functions** (aka Reagent components) which compute what UI DOM should be displayed for the user. They take data, delivered reactively by the queries of domino 4, and compute hiccup-formatted data, which is a description of the DOM required. More on hiccup soon. ### Domino 6 Domino 6 is not something you need write yourself - instead it is handled for you by Reagent/Rect. I mention it here for completeness and to fully close the loop. This is the step in which the hiccup-formatted "descriptions of required DOM", returned by Domino 5, are made real. The browser DOM nodes are mutated. ## A Simple Loop Of Simple Functions **Each of the dominoes you supply are simple, pure functions** which can be be described, understood and tested independently. They take data, transform it and return new data. The loop itself is utterly predictable and very mechanical in operation. So, there's a regularity, simplicity and certainty to how a re-frame app goes about its business, which leads, in turn, to an ease in reasoning and debugging. ## Managing mutation The two sub-cascades 1-2-3 and 4-5-6 have a similar structure. In each cascade, it is the 2nd to last domino which computes "descriptions" of mutations required and it is the last domino which actions these descriptions - it does the dirty work. And in both case, you need worry yourself about this dirty work. re-frame looks after those dominoes. ## Code Fragments Time to understand this domino narrative in terms of code fragments. > You shouldn't expect to fully grok all the code presented below. We're still in overview mode, getting the 30,000 foot view, and detail is missing. Plenty of tutorials and examples to follow. **Imagine:** the UI of an SPA shows a list of items. This user clicks the "delete" button next to the 3rd item in a list. In response, what happens within this imaginary re-frame app? Here's a sketch of the 6 domino cascade: ### Code For Domino 1 The delete button for that 3rd item will have an `on-click` handler (function) which looks like this: ```clj #(re-frame.core/dispatch [:delete-item 2486]) ``` `dispatch` is the means by which you emit an `event`. An `event` is a vector and, in this case, it has 2 elements: `[:delete-item 2486]`. The first element, `:delete-item`, is the kind of event. The `rest` is optional, and is whatever else needs to be known about the event - in this case, my made-up id, `2486`, for the item to delete. ### Code For Domino 2 The `event handler`, `h`, associated with `:delete-item` is called to compute the `effect` of this event. This handler function, `h`, must take two arguments: the state-of-the-world and the event, and it must return an effects map. Without going into any explanations at this early point, here's a sketch of what a handler might look like: ```clj (defn h [{:keys [db]} event] ;; args: db from coeffect, event (let [item-id (second event)] ;; extract id from event vector {:db (dissoc-in db [:items item-id])})) ;; effect is change db ``` Sometime earlier, this event handler (function) `h` would have been associated with `:delete-item` in this way: ```clj (re-frame.core/reg-event-fx :delete-item h) ``` ### Code For Domino 3 An `effect handler` (function) actions the `effect` returned by the call to `h`. That effect was the map: ```clj {:db (dissoc-in db [:items item-id])} ``` Keys in this map identify the required effect, with the values supplying further details. A key of `:db` means to update the app state, with the value supplied. This is a mutative step, facilitated by re-frame, which you won't have to do explicitly. ### Code For Domino 4 Because the app state changed, a query (function) over this app state is called automatically (reactively), and it computes the list of items (which, because of domino 3, has been updated to no longer contain the deleted item). Because the items are stored in app state, there's not a lot to compute in this case. This subscription acts more like an accessor. ```clj (defn query-fn [db _] ;; db is current app state (:items db)) ;; not much of materialised view ``` Such a query-fn must be registered, (reasons become obvious in the next domino) like this: ```clj (re-frame.core/reg-sub :query-items query-fn) ``` ### Code For Domino 5 Because the query function computed a new value, a view (function) which subscribes to that value, is called automatically (reactively) to re-compute DOM. It produces a hiccup-formatted data structure describing the DOM nodes required (no DOM nodes for the deleted item, obviously, but otherwise the same DOM as last time). ```clj (defn items-view [] (let [items (subscribe [:query-items])] ;; source items from app state [div: (map item-render @items])) ;; assume item-render already written ``` Notice how `items` is "sourced". A view function uses `subscribe` with a key originally used to register a query function. ### Code For Domino 6 The computed DOM (hiccup) is made real by Reagent/React. No code from you required. Just happens. The DOM "this time" is the same as last time, except for the absence of DOM for the deleted item. The key point to understand about 3-4-5-6 is that a change to app state, triggers queries to rerun, which, in turn, triggers views to rerun which, in turn, causes fresh DOM in the broiwser All reactively. Boom, boom, boom. One domino after the other. But with efficiency short circuits. ### Aaaaand we're done At this point, the re-frame app returns to a quiescent state, waiting for the next event. ## It Leverages Data You might already know that ClojureScript is a modern lisp, and that lisps are **homoiconic**. If not, you do now. The homoiconic bit is significant. It means you program in a lisp by creating and assembling lisp data structures. Think about that. You are **programming in data**. The functions which later manipulate data, start as data. Clojure programmers place particular emphasis on the primacy of data. When they aren't re-watching Rich Hickey videos, and wishing their hair was darker and more curly, they meditate on aphorisms like "Data is the ultimate in late binding". I cannot stress too much what a big deal this is. It can seem like a syntax curiosity at first but, when the penny drops for you on this, it tends to be a profound moment. And once you understand the importance of this concept at the language level, you naturally want to leverage similar power at the library level. So, it will come as no surprise, then, to know that re-frame has a data oriented design. Events are data. Effects are data. DOM is data. The functions which transform data are registered and looked up via data. Interceptors (data) are preferred over middleware (higher order functions). Etc. Data - that's the way we roll. ## It is mature and proven in the large re-frame was released in early 2015, and has since [been](https://www.fullcontact.com) successfully [used](https://www.nubank.com.br) by [quite](http://open.mediaexpress.reuters.com/) a [few](https://rokt.com/) companies and individuals to build complex apps, many running beyond 40K lines of ClojureScript. **Scale changes everything.** Frameworks are just pesky overhead at small scale - measure them instead by how they help you tame the complexity of bigger apps, and in this regard re-frame has worked out well. Some have been effusive in their praise. Having said that, re-frame remains a work in progress and it falls short in a couple of ways - for example it doesn't work as well as we'd like with devcards (which is a library vs framework issue) - we're still puzzling over some aspects and tweaking as we go. All libraries represent a point in the possible design space, with pros and cons. And, yes, re-frame is fast, straight out of the box. And, yes, it has a good testing story (unit and behavioural). And, yes, it works in with figwheel to create a delightful hot-loading development story. And, yes, it has a fun specialist tooling, and a community, and useful 3rd party libraries. ## Where Do I Go Next? We haven't yet looked at code, but **at this point you already know 40% of re-frame.** There's detail to fill in, for sure, but the core concepts are now known to you. Next, you need to do the code walk-through in the tutorial. This will get your knowledge to about 70%. The final 30% always comes incrementally with use and by reading the rest of the docs (of which there's a few). So, next, go here:
https://github.com/Day8/re-frame/blob/master/docs/README.md Experiment with these examples:
https://github.com/Day8/re-frame/tree/master/examples Use a template to create your own project:
Client only: https://github.com/Day8/re-frame-template
Front and back: http://www.luminusweb.net/ Use these resources:
https://github.com/Day8/re-com XXX ### Good news. If you've read this far, your insiders T-shirt will be arriving soon - it will feature turtles and [xkcd](http://xkcd.com/1416/). We're still working on the hilarious caption bit. Open a repo issue with a suggestion. ## Licence Copyright © 2014-2016 Michael Thompson Distributed under The MIT License (MIT) - See LICENSE.txt [SPAs]:http://en.wikipedia.org/wiki/Single-page_application [SPA]:http://en.wikipedia.org/wiki/Single-page_application [Reagent]:http://reagent-project.github.io/