{"id":18837996,"url":"https://github.com/nextjournal/freerange","last_synced_at":"2025-04-14T06:22:45.358Z","repository":{"id":46665605,"uuid":"228404795","full_name":"nextjournal/freerange","owner":"nextjournal","description":null,"archived":false,"fork":false,"pushed_at":"2021-10-01T08:09:52.000Z","size":5691,"stargazers_count":30,"open_issues_count":5,"forks_count":1,"subscribers_count":17,"default_branch":"master","last_synced_at":"2025-04-11T21:09:11.123Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nextjournal.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"license.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":"CITATION.md","codeowners":null,"security":null,"support":null}},"created_at":"2019-12-16T14:26:13.000Z","updated_at":"2024-09-17T02:32:36.000Z","dependencies_parsed_at":"2022-09-11T04:10:15.531Z","dependency_job_id":null,"html_url":"https://github.com/nextjournal/freerange","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nextjournal%2Ffreerange","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nextjournal%2Ffreerange/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nextjournal%2Ffreerange/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nextjournal%2Ffreerange/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nextjournal","download_url":"https://codeload.github.com/nextjournal/freerange/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248831028,"owners_count":21168388,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-08T02:37:27.084Z","updated_at":"2025-04-14T06:22:45.330Z","avatar_url":"https://github.com/nextjournal.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n\u003cimg src=\"/images/logo/re-frame_128w.png?raw=true\"\u003e\n\n## Derived Values, Flowing\n\n\u003e This, milord, is my family's axe. We have owned it for almost nine hundred years, see. Of course,\nsometimes it needed a new blade. And sometimes it has required a new handle, new designs on the\nmetalwork, a little refreshing of the ornamentation ... but is this not the nine hundred-year-old\naxe of my family? And because it has changed gently over time, it is still a pretty good axe,\ny'know. Pretty good.\n\n\u003e -- Terry Pratchett, The Fifth Elephant \u003cbr\u003e\n\u003e \u0026nbsp;\u0026nbsp;\u0026nbsp; reflecting on identity, flow and derived values  (aka [The Ship of Theseus](https://en.wikipedia.org/wiki/Ship_of_Theseus))\n\n[![CI](https://github.com/day8/re-frame/workflows/ci/badge.svg)](https://github.com/day8/re-frame/actions?workflow=ci)\n[![CD](https://github.com/day8/re-frame/workflows/cd/badge.svg)](https://github.com/day8/re-frame/actions?workflow=cd)\n[![GitHub tag (latest by date)](https://img.shields.io/github/v/tag/day8/re-frame?style=flat)](https://github.com/day8/re-frame/tags)\n[![Clojars Project](https://img.shields.io/clojars/v/re-frame.svg)](https://clojars.org/re-frame)\n[![GitHub issues](https://img.shields.io/github/issues-raw/day8/re-frame?style=flat)](https://github.com/day8/re-frame/issues)\n[![GitHub pull requests](https://img.shields.io/github/issues-pr/day8/re-frame)](https://github.com/day8/re-frame/pulls)\n[![License](https://img.shields.io/github/license/day8/re-frame.svg)](license.txt)\n\n## re-frame\n\nre-frame is a pattern for writing [SPAs] in ClojureScript, using [Reagent].\n\nMcCoy might report \"It's MVC, Jim, but not as we know it\".  And you would respond \n\"McCoy, you trouble maker, why even mention an OO pattern? \nre-frame is a **functional framework**.\"\n\nBeing a functional framework, it is about two things: data, and the functions \nwhich transform that data.  And, because it is a reactive framework, the [\"data coordinates the functions\"](https://www.youtube.com/watch?v=ZgqFlowyfTA\u0026t=80) (and not the other way around).\n\n## Why Should You Care?\n\nPerhaps:\n\n1.  You want to develop an [SPA] in ClojureScript, and you are looking for a framework.\n2.  You believe Facebook did something magnificent when it created React, and\n    you are curious about the further implications. Is the combination of\n    `reactive programming`, `functional programming` and `immutable data` going to\n    **completely change everything**?  And, if so, what would that look like in a language\n    that embraces those paradigms?\n3.  You're taking a [Functional Design and Programming course](http://www.eli.sdsu.edu/courses/fall15/cs696/index.html) at San Diego State University\n    and you have a re-frame/reagent assignment due.  You've left the reading a bit late, right?\n4.  You know Redux, Elm, Cycle.js or Pux and you're\n    interested in a ClojureScript implementation.\n    In this space, re-frame is very old, hopefully in a Gandalf kind of way.\n    First designed in Dec 2014, it even slightly pre-dates the official Elm Architecture,\n    although thankfully we were influenced by early-Elm concepts like `foldp` and `lift`, as well as \n    Clojure projects like [Pedestal App], [Om] and [Hoplon]. Since then,\n    re-frame has pioneered ideas like event handler middleware,\n    coeffect accretion, and de-duplicated signal graphs.\n5.  Which brings us to the most important point: **re-frame is impressively buzzword compliant**. It has reactivity,\n    unidirectional data flow, pristinely pure functions,\n    interceptors, coeffects, conveyor belts, statechart-friendliness (FSM)\n    and claims an immaculate hammock conception. It also has a charming\n    xkcd reference (soon) and a hilarious, insiders-joke T-shirt,\n    ideal for conferences (in design).  What could possibly go wrong?\n\n[OM]:https://github.com/swannodette/om\n[Hoplon]:http://hoplon.io/\n[Pedestal App]:https://github.com/pedestal/pedestal-app\n\n\n## It Leverages Data\n\nYou might already know that ClojureScript is a modern Lisp, and that\nLisps are **homoiconic**.  If not, you do now.\n\nThis homoiconic bit is significant. It means you program in a Lisp by creating and\nassembling Lisp data structures. Dwell on that for a moment. You are **programming in data**. \nThe functions which later transform data, themselves start as data.\n\nClojure programmers place particular emphasis on the primacy of data, and \nthey like to meditate on aphorisms like **data is the ultimate in late binding**. \n(Less productively, they also like re-watching Rich Hickey videos, and wishing\ntheir hair was darker and more curly)\n\nI cannot stress enough what a big deal this is. It may seem \nlike a syntax curiosity at first but, when the penny drops for \nyou on this, it tends to be a profound moment. And once you \nunderstand the importance of this concept at the language level, \nyou naturally want to leverage similar power at the library and system levels.\n\nSo, it will come as no surprise, then, to know that re-frame has a \ndata oriented design. Events are data. Effects are data. DOM is data.\nThe functions which transform data are registered and looked up via \ndata. Interceptors (data) are preferred over middleware (higher \norder functions). Etc.\n\nAnd, on a related arc, re-frame applications are reactive \nand that further elevates the importance of data because, in a reactive architecture, \nit is the arrival of data which [coordinates the calling of functions](https://www.youtube.com/watch?v=ZgqFlowyfTA\u0026t=80) (and not the other way around). \n\n**Data - that's the way we roll.**\n\n\n## It is a loop\n\nArchitecturally, re-frame implements \"a perpetual loop\".\n\nTo build an app, you hang pure functions on certain parts of this loop, \nand re-frame looks after the **conveyance of data** \naround the loop, into and out of the transforming functions you \nprovide - hence a tag line of \"Derived Values, Flowing\".\n\n### It does Physics\n\nRemember this diagram from school? The water cycle, right?\n\n\u003cimg height=\"350px\" align=\"right\" src=\"/images/the-water-cycle.png?raw=true\"\u003e\n\nTwo distinct stages, involving water in different phases, being acted upon\nby different forces: gravity working one way, evaporation/convection the other.\n\nTo understand re-frame, **imagine data flowing around that loop instead of water**.\n\nre-frame\nprovides the conveyance of the data around the loop - the equivalent of gravity, evaporation and convection.\nYou design what's flowing and then you hang functions off the loop at\nvarious points to compute the data's phase changes (again, data flow coordinates code).\n\nSure, right now, you're thinking \"lazy sod - make a proper Computer Science-y diagram\". But, no.\nJoe Armstrong says \"don't break the laws of physics\" - I'm sure\nyou've seen the videos - and if he says to do something, you do it\n(unless Rich Hickey disagrees, and says to do something else). So,\nthis diagram, apart from being a plausible analogy which might help\nyou to understand re-frame, is **practically proof** it does physics.\n\n## It is a 6-domino cascade\n\n\u003cimg align=\"right\" src=\"/images/Readme/Dominoes-small.jpg?raw=true\"\u003e\n\nComputationally, each iteration of the loop involves a\nsix domino cascade.\n\nOne domino triggers the next, which triggers the next, et cetera, boom, boom, boom, until we are \nback at the beginning of the loop, and the dominoes reset to attention \nagain, ready for the next iteration of the same cascade.\n\nThe six dominoes are: \n1. Event dispatch\n2. Event handling \n3. Effect handling \n4. Query\n5. View\n6. DOM\n\nLet's have a first look at each them. \n\n### 1st Domino - Event Dispatch\n\nAn `event` is sent when something happens - the user \nclicks a button, or a websocket receives a new message.\n\nWithout the impulse of a triggering `event`, no six domino cascade occurs.\nIt is only because of `event`s that a re-frame app is propelled,\nloop iteration after loop iteration, from one state to the next.\n\nre-frame is `event` driven.\n\n### 2nd Domino - Event Handling\n\nIn response to an `event`, an application must decide what action to take. \nThis is known as `event handling`.\n\nEvent handler functions compute how an event should change \"the world\",\nwhich is to say that they compute the `side effects` of the event.\nOr, more accurately, they compute a declarative **description of the required\n`side effects`**, represented as a data structure. (So `event handlers`\nare just functions which compute data).\n\nMuch of the time, an event will only cause `side effects` to the SPA's \n\"application state\", but sometimes the outside world must also be affected:\nlocalstore, cookies, databases, emails, logs, etc.\n\n### 3rd Domino - Effect Handling\n\nIn this step, the `side effects`, returned by the previous step (as data), are actioned/performed.\n\nNow, to a functional programmer, `effects` are scary in a \n[xenomorph kind of way](https://www.google.com.au/search?q=xenomorph).\nNothing messes with functional purity\nquite like the need for side effects. On the other hand, `effects` are \nmarvelous because they move the app forward. Without them, \nan app stays stuck in one state forever, never achieving anything.\n\nSo re-frame embraces the protagonist nature of `effects` - the entire, unruly zoo of them - but\nit does so in a controlled and largely hidden way, and in a manner which is debuggable, auditable, mockable and pluggable.\n\n### We're Now At A Pivot Point\n\nDomino 3 just changed the world and, very often, one particular part of it: the **application state**.\n\nre-frame's `app state` is held in one place - think of it like you \nwould an in-memory, central database for the app (details later).\n\nAny changes to `app state` trigger the next part of the cascade \ninvolving dominoes 4-5-6.\n\n### There's a Formula For It \n\nThe 4-5-6 domino cascade implements the formula made famous by Facebook's ground-breaking React library:  \n  `v = f(s)`\n\nA view, `v`, is a function, `f`, of the app state, `s`.\n\nSaid another way, there are functions `f` that compute which DOM nodes, `v`,\nshould be displayed to the user when the application is in a given app state, `s`.\n\nOr, to capture the dynamics we'd say: **over time**, as `s` changes, `f`\nwill be re-run each time to compute new `v`, forever keeping `v` up to date with the current `s`.\n\nOr, with yet another emphasis: **over time** what is presented to the user changes in response to application state changes. \n\nIn our case, domino 3 changes `s`, the application state,\nand, in response, dominoes 4-5-6 are concerned with re-running `f` to compute the new `v` \nshown to the user.\n\nExcept, of course, there are nuances.  For instance, there's no single `f` to run.\nThere may be many functions which collectively build the overall DOM, \nand only part of `s` may change at any one time, so only part of the \n`v` (DOM) need be re-computed and updated. And some parts of `v` might not \nbe showing right now.\n\n\n### Domino 4 - Query\n\n\u003cimg align=\"right\" src=\"/images/Readme/6dominoes.png?raw=true\"\u003e\n\nDomino 4 is about extracting data from \"app state\", and providing it \nin the right format for view functions (which are Domino 5).\n\nDomino 4 is a novel and efficient de-duplicated signal graph which \nruns query functions on the app state, `s`, efficiently computing \nreactive, multi-layered, \"materialised views\" of `s`.\n\nRelax about any unfamiliar terminology, you'll soon \nsee how simple the code actually is.\n\n(*`react-redux` programmers:* query functions serve essentially the same purpose as `mapStateToProps`)\n\n### Domino 5 - View\n\nDomino 5 is one or more **view functions** (aka Reagent components) that compute the \nUI DOM that should be displayed to the user.\n\nTo render the right UI, they need to source application state, which is\ndelivered reactively via the queries of Domino 4. They \ncompute hiccup-formatted data, which is a description of the DOM required.\n\n### Domino 6 - DOM\n\nYou don't write Domino 6 - it is handled for you \nby Reagent/React. I mention it here \nfor completeness and to fully close the loop.\n\nThis is the step in which the hiccup-formatted \n\"descriptions of required DOM\", returned by the view functions of Domino 5, are made real.\nThe browser DOM nodes are mutated. \n\n## Managing mutation\n\nThe two sub-cascades 1-2-3 and 4-5-6 have a similar structure.\n\nIn each, it is the second to last domino which \ncomputes \"data descriptions\" of mutations required, and it is \nthe last domino which does the dirty work and realises these descriptions.\n\nIn both cases, you don't need to worry yourself about this dirty work. re-frame looks \nafter those dominoes.\n\n### A Cascade Of Simple Functions\n\n**You'll (mostly) be writing pure functions** which \ncan be described, understood and \ntested independently. They take data, transform it and return new data.\n\nThe loop itself is mechanical and predictable in operation.\nSo, there's a regularity to how a re-frame app goes about its business,\nwhich leads, in turn, to an ease in reasoning and debugging.\n\n## The Dominoes Again - With Code Fragments\n\n\u003cimg align=\"right\" src=\"/images/Readme/todolist.png?raw=true\"\u003e\n\nSo that was the view of re-frame from 60,000 feet. We'll now shift down to 30,000 feet \nand look again at each domino, but this time with code fragments.\n\n**Imagine:** we're working on a SPA which displays a list of items. You have \njust clicked the \"delete\" button next to the 3rd item in the list.\n\nIn response, what happens within this imaginary re-frame app? Here's a sketch \nof the six domino cascade:\n\n\u003e Don't expect \nto completely grok the terse code presented below. We're still at 30,000 feet. Details later. \n\n### Code For Domino 1\n\nThe delete button for that 3rd item will be rendered by a ViewFunction which looks like this: \n```clj\n(defn delete-button \n  [item-id]\n  [:div.garbage-bin \n     :on-click #(re-frame.core/dispatch [:delete-item item-id])])\n```\n\nThat `on-click` handler uses re-frame's `dispatch` to emit an `event`.\n\nA re-frame `event` is a vector and, in this case, \nit has 2 elements: `[:delete-item 2486]` (where `2486` is the made-up id for that 3rd item).  \n \nThe first element of an event vector,\n`:delete-item`, is the kind of event. The rest is optional, useful data about the \n`event`.  \n\nEvents express intent in a domain specific way. \nThey are the language of your re-frame system. \n\n### Code For Domino 2\n\nAn `event handler` (function), called say `h`, is called to \ncompute the `effect` of the event `[:delete-item 2486]`.\n\nOn app startup, `re-frame.core/reg-event-fx` would have been used to \nregister this `h` as the handler for  `:delete-item` events, like this:\n```clj\n(re-frame.core/reg-event-fx   ;; a part of the re-frame API\n  :delete-item                ;; the kind of event\n  h)                          ;; the handler function for this kind of event\n```\n\n`h` is written to take two arguments: \n  1. a `coeffects` map which contains the current state of the world (including app state)\n  2. the `event` to handle\n\nIt is the job of `h` to compute how the world should be changed by the event, and \nit returns a map of `effects` - a description of those changes.\n\nHere's a sketch (we are at 30,000 feet):\n```clj\n(defn h                               ;; maybe choose a better name like `delete-item`\n [coeffects event]                    ;; `coeffects` holds the current state of the world.  \n (let [item-id (second event)         ;; extract id from event vector\n       db      (:db coeffects)]       ;; extract the current application state\n   {:db  (dissoc-in db [:items item-id])})) ;; effect is \"change app state to ...\"\n```\n\nre-frame has ways (described in later tutorials) to inject necessary aspects\nof the world into that first `coeffects` argument (map). Different \nevent handlers need different \"things\" to get their job done. But \ncurrent \"application state\" is one aspect of the world which is \ninvariably needed, and it is available by default in the `:db` key.\n\nBTW, here is a more idiomatic rewrite of `h` which uses `destructuring` of the args: \n\n```clj\n(defn h \n  [{:keys [db]} [_ item-id]]    ;; \u003c--- new: obtain db and id directly\n  {:db  (dissoc-in db [:items item-id])}) ;; same as before\n```\n\n\n### Code For Domino 3\n\nAn `effect handler` (function) actions the `effects` returned by `h`.\n\nHere's what `h` returned:\n```clj\n{:db  (dissoc-in db [:items 2486])}   ;; db is a map of some structure\n```\nEach key of the map identifies one kind \nof `effect`, and the value for that key supplies further details. \nThe map returned by `h` only has one key, so there's only one effect.\n\nA key of `:db` means to update the app state with the key's value.\n\nThis update of \"app state\" is a mutative step, facilitated by re-frame\nwhich has a built-in `effects handler` for the `:db` effect.\n\nWhy the name `:db`?  Well, re-frame sees \"app state\" as something of an in-memory \ndatabase. More on this in a following tutorial.\n\nJust to be clear, if `h` had returned: \n```clj\n{:wear  {:pants \"velour flares\"  :belt false}\n :tweet \"Okay, yes, I am Satoshi. #coverblown\"}\n```\nThen, the two effects handlers registered for `:wear` and `:tweet` would \nbe called to action those two effects. And, no, re-frame \ndoes not supply standard effect handlers for either, so you would have had \nto have written them yourself (see how in a later tutorial).\n\n### Code For Domino 4\n\nWe now start the `v = f(s)` part of the flow. \n\nThe application state\n`s` has just changed (via Domino 3) and now boom, boom go Dominoes 4, 5, \nand 6, at the end of which we have a new view, `v`, being shown to the user.\n\nIn this domino 4, a query (function) over this app state is automatically \ncalled.  This query function \"extracts\" data from application state, and \nthen computes \"a materialised view\" of the application state - producing\ndata which is useful to the view functions of domino, 5.\n\nNow, in this particular case, the query function is pretty trivial.\nBecause the items are stored in app state, there's not a lot \nto compute and, instead, it acts strictly like an extractor or accessor,\njust plucking the list of items out of application state:\n```clj\n(defn query-fn\n  [db v]         ;; db is current app state, v the query vector\n  (:items db))   ;; not much of a materialised view\n```\n\nOn program startup, such a `query-fn` must be associated with a `query-id`, \n(so it can be used via `subscribe` in domino 5) using `re-frame.core/reg-sub`, \nlike this:\n```clj\n(re-frame.core/reg-sub  ;; part of the re-frame API\n   :query-items         ;; query id  \n   query-fn)            ;; query fn\n```\nWhich says \"if, in domino 5, you see a `(subscribe [:query-items])`, then \nuse `query-fn` to compute it\".\n\n### Code For Domino 5\n\nBecause the query function for `:query-items` just re-computed a new value, \nany view (function) which uses a `(subscribe [:query-items])` \nis called automatically (reactively) to re-compute new DOM.\n\nView functions compute a data structure, in hiccup format, describing \nthe DOM nodes required. In this \"items\" case, the view functions will *not* be generating \nhiccup for the just-deleted item obviously but, other than this, \nthe hiccup computed \"this time\" will be the same as \"last time\".\n\n```clj\n(defn items-view\n  []\n  (let [items  (subscribe [:query-items])]  ;; source items from app state\n    [:div (map item-render @items)]))   ;; assume item-render already written\n```\n\nNotice how `items` is \"sourced\" from \"app state\" via `re-frame.core/subscribe`.\nIt is called with a vector argument, and the first element of that vector is\na query-id which identifies the \"materialised view\" required by the view.\n\nNote: `subscribe` queries can be parameterised. So, in real world apps\nyou might have this:\u003cbr\u003e\n  `(subscribe [:items \"blue\"])`\n\nThe vector identifies, first, the query, and then\nsupplies further arguments. You could think of that as \nrepresenting `select * from Items where colour=\"blue\"`.\n\nExcept there's no SQL available and you would be the one to implement\nthe more sophisticated `query-fn` capable of handling the \n\"where\" argument. More in later tutorials.\n\n### Code For Domino 6\n\nThe hiccup returned by the view function \nis made into real browser DOM by Reagent/React. No code from you required. Just happens.\n\nThe DOM computed \"this\ntime\" will be the same as \"last time\", **except** for the absence of DOM for the\ndeleted item, so the mutation will be to remove those now-missing\nDOM nodes from the browser.\n\n### 3-4-5-6 Summary\n\nThe key point to understand about our 3-4-5-6 example is:\n  - a change to app state ...\n  - triggers query functions to rerun ...\n  - which triggers view functions to rerun\n  - which causes modified browser DOM \n\nBoom, boom, boom go the dominoes. It is a reactive data flow.\n\n### Aaaaand we're done \n\nAt this point, the re-frame app returns to a quiescent state, \nwaiting for the next event.\n\n## So, your job is ... \n\nWhen building a re-frame app, you:\n - design your app's information model (data and schema layer)\n - write and register event handler functions  (control and transition layer)  (domino 2)\n - (once in a blue moon) write and register effect and coeffect handler\n   functions (domino 3) which do the mutative dirty work of which we dare not\n   speak. \n - write and register query functions which implement nodes in a signal graph (query layer) (domino 4)\n - write Reagent view functions  (view layer)  (domino 5)\n\n\n## re-frame is mature and proven in the large\n\nre-frame was released in early 2015, and has since \n[been](https://www.fullcontact.com) successfully\n[used](https://www.nubank.com.br) by\n[quite](http://open.mediaexpress.reuters.com/) a \n[few](https://rokt.com/) companies and\nindividuals to build complex apps, many running beyond 40K lines of\nClojureScript.\n\n\u003cimg align=\"right\" src=\"/images/scale-changes-everything.jpg?raw=true\"\u003e\n\n**Scale changes everything.** Frameworks\nare just pesky overhead at small scale - measure them instead by how they help\nyou tame the complexity of bigger apps, and in this regard re-frame has\nworked out well. Some have been effusive in their praise.\n\nHaving said that, re-frame remains a work in progress and it falls\nshort in a couple of ways - for example it doesn't work as well as we'd\nlike with devcards, because it is a framework, rather than a library. \nWe're still puzzling over some aspects and tweaking as we go. All designs\nrepresent a point in the possible design space, with pros and cons.\n\nAnd, yes, re-frame is fast, straight out of the box. And, yes, it has \na good testing story (unit and behavioural). And, yes, it works with your build\ntools (like figwheel or shadow-cljs) to create\na powerful hot-loading development story. And, yes, it has \nfun specialist tooling, and a community,\nand useful 3rd party libraries.\n\n## Where Do I Go Next?\n\nAt this point, you know 50% of re-frame.  \u003cbr\u003e\nThe full [docs are here](/docs/README.md) and the [FAQs are here](/docs/FAQs/README.md).\n\nThere are two example apps to play with: \u003cbr\u003e\nhttps://github.com/day8/re-frame/tree/master/examples\n\nUse a template to create your own project: \u003cbr\u003e\nClient only:  https://github.com/day8/re-frame-template  \u003cbr\u003e\nFull Stack: http://www.luminusweb.net/\n\nAnd please be sure to review these further resources: \u003cbr\u003e\nhttps://github.com/day8/re-frame/blob/master/docs/External-Resources.md\n\n### T-Shirt Unlocked\n\nGood news.  If you've read this far,\nyour insiders T-shirt will be arriving soon - it will feature turtles, \n[xkcd](http://xkcd.com/1416/) and something about \"data all the way down\". \nBut we're still working on the hilarious caption bit. Open a\nrepo issue with a suggestion.\n\n[SPAs]:http://en.wikipedia.org/wiki/Single-page_application\n[SPA]:http://en.wikipedia.org/wiki/Single-page_application\n[Reagent]:http://reagent-project.github.io/\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnextjournal%2Ffreerange","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnextjournal%2Ffreerange","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnextjournal%2Ffreerange/lists"}