GithubHelp home page GithubHelp logo

day8 / re-frame Goto Github PK

View Code? Open in Web Editor NEW
5.4K 147.0 716.0 75.62 MB

A ClojureScript framework for building user interfaces, leveraging React

Home Page: http://day8.github.io/re-frame/

License: MIT License

Clojure 97.73% JavaScript 0.42% HTML 1.85%
reagent clojurescript re-frame spa react

re-frame's Introduction

re-frame logo

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 (aka The Ship of Theseus)



Overview

re-frame is a ClojureScript framework for building user interfaces. It has a data-oriented, functional design. Its primary focus is on high programmer productivity and scaling up to larger Single-Page applications.

Developed in late 2014, and released in 2015, it is mature and stable. It is used by both small startups and companies with over 500 developers, and it has delivered into production applications which are 40K lines of code and beyond.

Across the last 6 years, it has outlasted multiple generations of Javascript churn - just imagine your team's productivity if you didn't have to contend with technical churn, and have new magic burn your fingers every two years. Brand new, exciting concepts like recoiljs (in the React world), have been a regular part of re-frame from the beginning.

re-frame is lucky enough to enjoy an unfair advantage - ClojureScript is a Lisp. Alan Kay once described Lisp as "Maxwell's equations of software". Paul Graham described how Lisp was a competitive advantage for his startup. When we use Lisp, we get to leverage 50 years of foliated excellence from the very best minds available. And then there's also a thriving ClojureScript community which delivers modern ideas and best-in-class tooling.

Although re-frame leverages React (via Reagent), it only needs React to be the V in MVC, and no more. re-frame takes a different road to the currently-pervasive idea that Views should be causal (colocated queries, ComponentDidMount, hooks, etc). In re-frame, events are causal, and views are purely reactive.

Documentation

The re-frame documentation is available here.

The Current Version

Clojars Project

For full dependency information, see the Clojars page

Getting Help

Get help on Slack

Licence

re-frame is MIT licenced

re-frame's People

Contributors

bpringe avatar cloojure avatar danielcompton avatar deg avatar dexterminator avatar dijonkitchen avatar gadfly361 avatar grahack avatar green-coder avatar gregg8 avatar hipitihop avatar jahson avatar kimo-k avatar lxsameer avatar manuel-uberti avatar martinklepsch avatar mbertheau avatar mike-thompson-day8 avatar nberger avatar oliyh avatar pesterhazy avatar phronmophobic avatar prestancedesign avatar richardharrington avatar scgilardi avatar si14 avatar smahood avatar stig avatar stumitchell avatar superstructor avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

re-frame's Issues

A simple chat example using re-frame and websockets

Hi there,

I'd like to thank you for renewing my interest in Reagent and Cljs by re-framing (no pun intended) how I was looking at the architecture of my applications.

I've just put together a simple chat application that uses re-frame, rethink and websockets for a dev team presentation here: https://bitbucket.org/lskibinski/frp-demo/

Everyone who uses Clojure and Clojurescript seem way smarter than I am so I'm not sure how much of a useful example it will be, but I'd thought I'd be put it out there for consideration.

Feature: let (dispatch ...) take multiple args

Sometimes I want to dispatch more than one handler at a time. Seems like it would be rather trivial to modify dispatch to take an indefinitely number of args and then dispatch each one. Something like

(defn dispatch [&event-vs]
  (doseq [event-v event-vs]
    (if (nil? event-v)
      (warn "re-frame: \"dispatch\" is ignoring a nil event.")     ;; nil would close the channel
      (put! event-chan event-v)))
  nil)

What do you think?

Integration of JS libraries

I wonder if there is a better way to integrate JS libs (in my case for graph drawing) into the framework? Currently I turn every change in a subscription into a handler event (using reagent.ratom/run!).
The event then does the JS calls, so it looks like this:

(defn sub->handler [handler sub & args]
  (let [x (re-frame/subscribe sub)]
    (ratom/run! (re-frame/dispatch (into [handler @x] args)))))
(re-frame/register-handler
 :foo-event
 (fn [db [_ subscription-result elem]]
    ;; call some JS stuff to change elem 
   db))
(defn component [x]
  (reagent/create-class
   {:component-did-mount
    (fn [c]
      (let [elem (.getDOMNode c)]
        (subs->handler :foo-event [:bar-subscription x] elem)))
    :reagent-render (fn [] [:div {:id "component"}])}))

reaction

I have more like a question than bug report.
Can I just subscribe to some subscription without producing hiccup and messing up with React itself? All I want is to listen to some change in model and update some complicated JS component (CodeMirror).
Maybe the place to ask is Reagent repo but maybe you know how to deal with it.
This will not work:

(register-sub
  :editor
  (fn [db _]
    (get-in db [:shared-model :editor])))

(let [s (subscribe [:editor])]
  (reaction
    (println @s)))

Thanks in advance.

Complete Release Notes for v0.2.0-alpha1

The release notes are currently as follows ... what else? Code is in develop branch ...

Breaking Renames:

  • register-pure-handler renamed to register-handler (and existing low level register-handler becomes register-handler-base but is not a part of the API).
  • remove apply-event middleware and replace with similar trim-v
  • rename register-subs to register-sub (avoid confusion over possible plurals)
  • rename set-max-undos to set-max-undos!

Breaking Change:

  • undoable middleware is now a factory. Where before you used this undoable, you must now use this (undoable "some explanation"). See further below.

Headline New Features

  • new example: todomvc available in examples folder.
  • Wiki documentation is now more substantial.
  • introduce some new handler middleware: debug, enrich and after

Improvements:

  • exceptions in a go loop are a special type of hell. Improve the reporting of exceptions happening in event handlers.
  • allow Middleware to be registered as a vector. data > functions > macros
  • fix two bugs in undo implementation
  • name licence file correctly, thanks to @smith
  • fix typo in readme, thanks to @btheado
  • Readme now admits to 200 lines of code, not 100.
  • dispatch now explicitly returns nil
  • travis integration (not that we have any tests currently)

Undo Changes

We've made changes so that the undo/redo feature is more powerful. Associated with each undo state is an explanation which can be presented to the user to inform them as to the actions they will be undoing or redoing.

Previously undoable was simply middleware, but it is now a middleware factory.

Essentially that means you can't use it "plain" anymore, you must call it to get middleware (put it in ()) and, when you do, supply a parameter which is an explanation for the mutation. "Set spam flag to yes", "add todo", etc.

When the time comes to present undos to the user, you then have an explanation associated with each one.

The explanation provided to undoable must be either a string (static explanation) or a function (db event) -> string (allowing you to customize the undo message based on details of the event. "Added todo called blah blah blah").

Planned for the next iteration, v0.2.0-alpha2

  • automatically wrap subscriptions in a reaction (removing the need for you to do it)
  • produce a more fully featured todomvc (beyond the standard one), todomvc-with-extras
  • begin to use goog.Logger

Still to happen before release

In todomvc, to be done tomorrow ahead of release:

  • fix bug in todomvc which means the footer disappears and doesn't come back.
  • add localstorage
  • add history, back button etc.

Datascript example

Hi,
I wonder how i can use datascript with re-frame ?
Is there an example somewhere ?
Many thanks,

Samuel

Write short and clean readme

I came here because in the clojurescript groups there lately is a lot of talk about re-frame.
I am sorry if this isn't what you want to be reading, but I attempted a second time to get an idea what your library does, so far without looking at the source code, and I believe that

  1. Spending the entire evening or even longer reading a 1000 lines of README to understand what I am even looking at or am going to understand is not worth my or any ones time, even if your library implemented the theory of everything
  2. Philosophical quotes and laissez-faire statements mixed with abstract concepts and insane paragraphs like "Correct acronym?", this one followed by a paragraph titled "Mostly a mashup" before any concrete code example or diagram aren't helping, either.
  3. The paragraph "What problem does it solve" lacks a clear problem statement.

I get that your library is about coordinating data flow in a web client written with reagent. Why don't you simply illustrate that with a few examples showing things in reagent (and preferably om as well) that you can't do without re-frame or that are at least typical to stumble upon, accompanied by a few better looking examples using re-frame? And put all the rest on a separate documentation page or into a wiki.

`lein figwheel` fails in example projects - can't find re-frame:re-frame:jar

$ git clone [email protected]:day8/re-frame
Cloning into 're-frame'...
remote: Counting objects: 711, done.
remote: Compressing objects: 100% (48/48), done.
remote: Total 711 (delta 20), reused 0 (delta 0), pack-reused 655
Receiving objects: 100% (711/711), 198.95 KiB | 0 bytes/s, done.
Resolving deltas: 100% (359/359), done.
Checking connectivity... done.
$ cd re-frame
$ git rev-parse HEAD
76068c2cb9b9975166a490ed101d086025042480
$ cd examples/simple/
$ lein figwheel
Could not find artifact re-frame:re-frame:jar:0.1.8 in central (https://repo1.maven.org/maven2/)
Could not find artifact re-frame:re-frame:jar:0.1.8 in clojars (https://clojars.org/repo/)
This could be due to a typo in :dependencies or network issues.
If you are behind a proxy, try setting the 'http_proxy' environment variable.

Put in checks for dispatch-sync

dispatch-sync can't be called from within an event handler - only an async dispatch can be "called".

Warn if this is ever attempted.

Why is it a problem? Well the the first event handler is given a snapshot of the db as a parameter. And whatever this first event handler returns will be put back into app-db when it finishes. So any changes made by an intermediate call to dispatch-sync will be lost.

So the problem sequence would be:

  1. first event handler called with db snapshot
  2. dispatch-sync called, making changes to app-db
  3. first event handler finishes, and its returned db is written into app-db

Ie. the changes in step 2 are lost. (no longer in app-db)

Dispatch handler's first argument parameter isn't a dereffed atom value

According to the docs,
"An event handler is a pure function of two parameters: current value in app-db. Note: that's the map in app-db, not the atom itself. 2 an event (represented as a vector)"

(defn handle-delete
[app-state [_ cid]] ;; notice how event vector is destructured -- 2nd parameter
(.log js/console app-state))

app-state currently returns a reagent ratom.

This caused me major headaches understanding why
(assoc app-state :first "hello)
wouldn't work.

Also, any idea when re-frame will be up on clojars?

FAQ #2 incorrect

I've tried two versions (using re-frame 0.4.0 and 0.4.1). The subscription :edited does NOT work whereas the :edited-org does. Is it problem with user or FAQ?

(register-sub
:trainings
(fn [db _](reaction %28vals %28:trainings @db%29%29)))

(register-sub
:editing
(fn [db _](reaction %28:editing @db%29)))

(register-sub
:edited
(fn [db _](let [trainings %28subscribe [:trainings]%29
editing %28subscribe [:editing]%29]
%28reaction %28get @trainings @editing {}%29%29)))

(register-sub
:edited-org
(fn [db _](reaction
%28let [trainings %28:trainings @db%29
editing %28:editing @db%29]
%28get trainings editing {}%29%29)))

Yes!

Hi -

I would like to congratulate you on this new project, and I am eagerly looking forward to your progress.

I don't have much experience yet programming ClojureScript and Reagent, but I've been reading up on this stuff for the past few months, and I hope start getting more involved soon once I understand it better.

I must say that the ReadMe for re-frame was one of the best tutorials I've ever seen on this subject.

Re-frame gets so many things right!

Prefers reagent instead of the other Clojurescript/React libraries... yes!

Views CRUD as a kind of discrete, dynamic, asynchronous, push FRP... yes!

Avoids om's cursors... yes!

Defines a single db (like an SQL schema) rather than a bunch of classes (like in O-O)... yes!

Pays homage to elm-lang... yes!

Views a ratom not as immutable data, but as a value that changes over time (eg, a signal)... yes!

Nice long conceptual / tutorial ReadMe... yes!

"So much incidental complexity evaporates."... yes!

The re-frame ReadMe is one of the best tutorials available regarding FRP-like programming

I must say, this is the first time I every really "got" all this stuff about React, FRP etc. - and I've been reading everything I can get my hands on for months (Elm-lang, Bacon, ThreePenny, Apfelmus, Reactive Banana, the various React / Flux frameworks and libraries, the various ClojureScript wrappers over them).

This lengthy, well-thought-out, heavily referenced, clearly written ReadMe which you have provided is a major contribution to the understanding of "reactive" programming in the browser. This is a new topic, which many front-end JavaScript programmers are struggling with as people try to make the transition from MVC to something resembling FRP.

People are coming up against a lot of conceptual barriers - and the excellent tutorial which you provided will clear up a lot of confusion. It was a big help to me. In fact, it's the first time I ever really "got" what's going on with FRP-like programming. (In my case, the lightbulb lit up when I realized that re-frame's computation is similar to a "dependency" in an older version of the functional language K I had studied years back - more details on this below. Somehow this had never occurred to me while reading any of the other tutorials and papers on FRP. But re-frame's ReadMe really helped make the connections.)

Related concepts in theoretical computer science

Ultimately I suspect that the asynchronous event-handling we're dealing with in JavaScript front-end frameworks and libraries involves what some theoretical computer scientists call "co-algebraic process types" (perhaps also called "streams" in other contexts) - which would be dual to "algebraic data types".

The "alegbraic data type" literature has percolated from academia to the world of practical programming - we do induction on algebraic data types all the time, it's called "recursion".

But the "co-algebraic process type" literature remains pretty much only in the academic world, and we don't often (explicitly, formally) do "co-induction" on "co-algebraic process types" - which seems to be what the older MVC JavaScript front-end frameworks (as well as the newer React/Flux JavaScript front-end frameworks) are (somewhat informally or naïvely) trying to deal with.

https://www.google.com/?gws_rd=ssl#q=coalgebra+streams+jan+rutten+nijmegen

A related implementation in an older version of the K language

Meanwhile, a blast from the past:

You may have heard of a language called K - a tiny, lightning-fast, functional, vector-processing (or columnar database) interpreted language in the APL family, also somewhat related to LISP. It's not very well known, but it's used by a handful of investment banks. Anyways, an earlier version of K happened to include a feature called "dependencies" (as well as "triggers"). It's interesting to note that a dependency in K appears to be fairly similar to a computation in re-frame (or a signal in elm-lang, etc.).

Below is an archived PDF of an early version of the K reference manual, which describes "dependencies":

http://web.archive.org/web/20050504070651/http://www.kx.com/technical/documents/kreflite.pdf

You can search the PDF for occurrences of "dependenc" (to match either "dependencies" or "dependency") as well as "trigger".

A time-limited trial version of the K interpreter may still also be floating around online somewhere. I can also send a copy if anyone is interested. There is also a newer version of the language out now, called Q or KDB, which also has a trial version, but it no longer includes the wonderful dependency / trigger features of that earlier version of K. The newer version does however apparently implement something similar, called views. I imagine they got rid of dependencies and triggers in K because they also involved a native GUI (in Windows and Linux), which actually worked fine at the time, but which might have been a hassle to keep up-to-date with OS upgrades.

There is also a simple example of a dependency in K on this page:

http://88.97.16.226/apl/APL/Reviews/kreview.htm

A major development in front-end programming

It is encouraging to see that web front-end developers now starting to use these efficient and easy-to-reason-about "dataflow" constructs / architectures which have occasionally popped up in a few functional languages in the past.

This is one of the things that makes ClojureScript such a great language: functional / immutable data structures + the syntactic power of LISP (able to manipulate abstract syntax trees directly by defining macros). You always have the abstract syntax tree available, parsable - so you can define something like re-frame's computation macro to act on it, and have the computation macro implement this sort of dataflow or FRP style of programming (instead of doing something clunky in a language which merely lets you call eval on a monolithic - ie, unparsed - string).

Lein template for simple example app

I've set up a lein template project for re-frame which emits the simple example app, parameterized with the project name: https://github.com/dparis/re-frame-template

I know it's still early-days for re-frame, but I thought it'd be nice to help the intrepid few get started easily.

I haven't yet deployed it to clojars, as I wanted to see how you might like to coordinate the release of this. If you'd like to assume ownership of the repo, just let me know and I'll transfer it over. Otherwise, let me know if there's anything you'd like me to update before I push it to clojars.

Can't wait to start using re-frame. Cheers!

reaction doesn't trigger a re-render for array

First of all, thanks for this great "framework" 👍 , just been aware of it yesterday and already spent the next 20hours on it (it's really fun to play with :))

Anyway, I have an app-db like this

(def app-db
  (->> (range 2015 2023)
       (map #(vector % (int-array 10 0)))
       (into {})
       (assoc other-data :finance)))

Somehow the subscription/reaction doesn't work for array (not reactive enough, the dispatch fn changed the app-db, yet doesn't trigger re-render components that subscribed to it). It is however changed once I navigate to other page and then go back to that page (an act which mutate the value of app-db). When I changed it to a vector then it works as expected.

I haven't look into the source code yet, but my guess is that the mutation inside array-cells won't be counted as an object change since the reference to the object remain unchanged and thus do not trigger re-rendering.

If it's intended to be that way so be it (it enforces value-based state anyway, instead of a reference-based). But just in case it wasn't, then a heads-up would probably be nice (I'm a rookie in this front-end world, so probably it's just me that is unaware of this behaviour).

Thanks

(un) registration

This is very interesting work. One thing I was thinking about was dynamic registration / unregistration of event handlers. How do you feel about this? Would you take a pull request for unregistration?

Fix Logging And Error Reporting

At the moment, re-frame makes use of various logging functions like re-frame.utils/warn.

But functions in this ns are currently too hard coded and inflexible. There's been a couple of suggestions around improving that situation: #43 and #34

But everyone's idea about logging is different. And re-frame is too low level to be opinionated in this area.

I'm personally not keen on goog's logging but others like it and want to use it. And on IE you can't use console.group because it doesn't exist. Etc.

So I propose that re-frame has a bunch of standard reporting functions defined, each with a default implementation, but provides a way for apps to define their own implementation.

(re-frame.core/set-logging! my-warn my-log my-error my-group my-ungroup)

**Notice that error has been added to the list -- for exceptions.

These alternative implementations can then be as complicated as needed and use goog.logging as required.

register-sub middleware

You probably get asked for this a lot but would be cool if one could use the same or similar middleware for register-sub too, at least path and trim-v.

Often when dealing with page-specific stuff one writes stuff like

(def path [:page :login])
(def path-m (re-frame.core/path path))

(register-handler
  ::input-changed
  path-m
  (fn [db' [_ value]]
    (assoc db' :value value)))

(register-sub
  ::input
  (fn [db _]
    (reaction (get-in @db (conj path :value)))))

But it would be cool to be able to derive the db in register-sub too

(register-sub 
  ::input
  path-m
  (fn [db' _]
    (reaction (:value @db'))))

Editing input fields

When running the simple example, if the color textbox is edited anywhere before the end of the input, when the input is changed it will reset the cursor to the end of the input box.

Any idea how to fix this? I used it as an example for how to save form input and am running into the same problem in my own app as well.

Add a way to purge redos

When an unhandled exception occurs (in production, gulp) we trigger an "undo" to get the user back into a sensible state, and then put up a modal.

But we also need to purge the redos list, so the last undo action is gone.

So, give a way to do something like this:
(dispatch [:purge-redos])

Yes! #2

Currently finishing off the "http://www.amazon.com/Constructing-User-Interface-Statecharts-Horrocks/dp/0201342782" book due to the indepth ReadME posted.
I too have had that 'click' with regards to all these funky words I've been listening to lately.
I'm going to refactor the whole of my dissertation done in pure reagent/reagent forms because of your framework.

Better yet, I'm going to have actual documentation and a walkthrough that includes statecharts!
I can't say thank you enough.
My dissertation is a "Positive Psychology and Wellbeing mobile web application" which is a bit ironic.
It's basically a reactive text based flow application presenting the user with the next three options to work on their lowest scored core life areas.

Being in my final year, I would also love to learn off you (lot?) or help out in any way possible!

Consider using more of goog.* Closure library for debugging/logging

I've notice a little comment in your notes that you're wondering about better logging/debugging mechanism. I'd like to point you to:
https://github.com/google/closure-library/blob/master/closure/goog/demos/debug.html

The demo seems to not work anymore on the offical google closure docs:
http://docs.closure-library.googlecode.com/git/class_goog_debug_FancyWindow.html

So the easiest is to clone google closure repo and just open the debug.html in a browser.

Advantages:

  • No deps, you already have all of google closure
  • Flexible & tested
  • Keeps emitted JS code small due to closure compiler & libarary

The logging of google closure is actually quite nice & flexible. For instance you can send exceptions automatically to the web server if wanted. Can probably also be easily adjusted to log to airbrake etc.

Feel free to ignore & close this issue.
HTH

Schema in todomvc

The readme recommends using the schema for app-db but the todomvc example does not use prismatic/schema. I tried but I am stumbling on the sorted-map. Can someone add this to the example?

Use pre and post conditions instead of manual validations

I'm not sure if this is a good idea or not, but I'm opening it up for discussion. Through the re-frame codebase there are validations done like https://github.com/Day8/re-frame/blob/master/src/re_frame/utils.cljs#L41-L45 which are really validating pre-conditions on the arguments. The intent of the code would be clearer if preconditions were used, although that brings up the question of how you want to handle errors when preconditions fail.

Thoughts?

go blocks in handlers?

Is it ok to have a go block inside a handler?

My use case is a secretary handler that must wait for data from the server, then do some other stateful stuff. I'm planning to have something like (go (<! loaded-from-server) (stateful-stuff!)) inside the secretary handler, since it's one of the first things that's run and must wait for the data.

If this isn't idiomatic, please suggest a better method! Thanks for making re-frame.

re-frame + pyccoon

Pyccoon is a Marginalia-like tool, to which I'm trying to add support for hyperlinks in the source (currently only for clojure and clojuresccript).
I used re-frame as test project, and would like your feedback. Link here (this was made with my personal branch, not the official one)
Do you like the style? Are the hyperlinks useful?

Textarea loses caret position on render

Hi, I have a problem with a databound textarea. If I edit text in the middle of the string the cursor will immediately leap to the end. Note that this does not happen if I just use a regular reagent atom to manage the state so I am assuming that it is something that re-frame is doing.

For example the following does not exhibit the problem:

(defn test-text2 []
  (let [text (atom {:text ""})]
    (fn []
      [:div 
       [:div (:text @text)]
      [:textarea {:id "test-text" 
                  :rows 10
                  :value (:text @text)
                  :key "test-text"
                  :on-change #(swap! text assoc :text (.. % -target -value))}]])))

But this code does:

(defn test-text3 []
  (let [text (subscribe [:test])]
    (fn []
      [:div 
       [:div @text]
       [:textarea {:id "test-text" 
                   :rows 10
                   :value @text
                   :key "test-text"
                   :on-change #(dispatch [:test (.. % -target -value)])}]])))

In this case the :test subscription and handler are just roundtriping a simple value to the re-frame state.

Is there something I am doing wrong here? Is this expected behaviour? It's really causing me a problem because I'm having to resort to all sorts of horrible hacks to get round it (and still not really satisfactory). If I could get the second example to work like the first it would be great.

Hope you can help because everything else about re-frame seems fantastic.

Thanks.

Write Wiki docs explaining how to do testing

re-frame v0.3.0, v0.4.0 v0.5.0 will allow each of the parts to be nicely tested (all without any async problems). Or, at least that's the cunning plan.

Explain how to do each part:

  • db
  • handlers
  • subscriptions
  • components

README.md -- where is :name-query defined?

The README references

(let [name-ratom (subscribe [:name-query])]

and later defines :customer-query, but not :name-query. Is this intentional, an error, or am I just missing something obvious here?

Docs missing a statechart guide

I'm a refugee of big javascript MVC frameworks (Sproutcore in my case). But one thing I loved about SproutCore was how it made statecharts a first-class interaction model. Entering states registered appropriate event handlers (click, etc), provided nesting, concurrent execution & history states, essentially everything you'd like to see in a mature statechart library. What's the clojurescript equivalent, if any? Could any pointers be included in the guide? I see you referenced Automat previously, but it seems to be at a FSM level, not a hierarchical state machine level. Stativus pulls the SproutCore statechart framework out into a stand-alone library though I've not used it. Is it too early for any concrete recommendations?

re-frame cookbook

Would it be possible to start a re-frame cookbook, similar to the ones for reagent and om?

Exceptions In Handlers - Still A Problem

Exceptions in event handlers are still a problem. I still haven't got it right.

I had hoped that log-ex (middleware) was the solution, but I was young and foolish. log-ex is indeed useful at reporting reporting an actionable exception into console including stack trace, but it rethrows the exception and that destroys the enclosing go-loop which is processing events. No event is ever processed again after such an exception. The app is totally broken.

That destruction is not so bad for a dev environment. But I want my production env to be slightly more robust in the face of unhandled exceptions, if possible. I want the app to at least have a fighting chance of survival, whereas, at the moment, it is always dead for any UHE (in an event handler).

Now, in production, I guess I could replace log-ex with other middleware which doesn't re-throw.

But I think the better solution is:

  • to have re-frame itself, not middleware, catch exceptions. It is re-frame's responsibility to guard the integrity of its go-loop. It shouldn't rely on the correct actions of apps using re-frame (putting in the right middleware)
  • when it catches an exception it should log/report it (See #53)
  • but not rethrow any exception, just swallow them, thus keeping the event loop alive.

Usage Notes

@lorddoig raised the following in #43. Transferred here for further discussion so as to not overload the original ticket with too many threads of thought ...


On another note - I really like re-frame and using it pretty heavily has led to some other thoughts (this is all subjective and sorry to hijack the issue, can open a separate one if you agree with any of the following):

  1. In pursuit of readability and "elegance" I find it much better to define a named function and then register it as a handler/subscription later, but this creates non-trivial cognitive load in having to track functions that are or aren't registered and in the fact that I can (even if I arguably shouldn't) allow the keyword event ID to differ from the function name.

  2. I habitually use the trim-v middleware on almost all handlers, and I frequently get tripped up by the fact that the middleware doesn't apply to subscriptions, 99% of which look like (defn some-sub [db [_ x]]) so maybe there's a case for extending the middleware concept to those

  3. I know middleware has to be declared outside the handler body so re-frame knows what to do with the return value, but this separation of logic seems less than ideal. It makes sense in the context of e.g. webapp routes because an app typically only has one set of routes so you can be relatively sure that when you find them you'll find where they're wrapped within a few lines 99% of the time. This isn't true for a multitude of handlers possibly spread across namespaces.

  4. Making relatively trivial setters and getters requires 4 forms:

    1. (defn set-thing [db _])
    2. (defn get-thing [db _])
    3. (register-handler :set-thing trim-v set-thing)
    4. (register-sub :get-thing get-thing)

    Of course these can often be parameterized but that doesn't quite solve everything.

With regard to these points I think there's perhaps a case for making a few macros that covers some of them (e.g. defhandler, defsub), or doing something akin to sente for handlers i.e. a multimethod that could look something like

(defmulti handle (fn [_ _ meta] (:id meta)))

(defmethod handle :set-thing
  [db ?data {:keys [id]}]
  (assoc db :thing (do-something ?data))

(re-frame.core/set-handler! handle)

Putting all stuff not immediately pertinent to the domain problem in a map as the last arg seems a friendly and extensible solution. This doesn't play so nice with middleware as it is now, but with regard to point 3 - what if middleware could be applied in the function body with a wrap macro that told re-frame what to do in the return value metadata? Of course this still leaves the subscriptions question open.

Thoughts? Feel free to call me out on any of this and tell me I'm doing it wrong.

Components re-rendering even though their data hasn't changed

Hi, first of all, thanks for sharing re-frame! It looks like it answers a lot of the problems we've had while working out how to write cljs/react SPAs. Very exciting.

However, I've been having a play with it today and am running into some problems - I'm probably missing or misunderstanding something obvious.

I've pushed the toy app I've been playing here: https://github.com/rsslldnphy/re-frame-issue

Basically it has one view two forms and a header which says "Hello, ". Each of the two forms, and the header, use separate subscriptions to different parts of the global state. However, whenever I make any change to the global state (by clicking "login" to change to header to say "Hello, russell", or by entering something into one of the form field and leaving it causing an on-blur to fire) both forms always re-render (as you can see from the logging in the console).

Am I right in saying that if the content of a reaction hasn't changed, the components shouldn't re-render? Or if not, what's the correct way to prevent re-rendering of components whose referenced reactions haven't changed?

Detect Mistaken Use Of Middleware Factories

A handler with faulty middleware specification silently fails to fire without error or console warning. The following unit test shows a handler using the undoable middleware factory incorrectly.

(ns test.base.faulty-middleware
  (:require
    [cemerick.cljs.test :refer-macros [is deftest use-fixtures testing]]
    [re-frame.core      :refer [trim-v undoable]]
    [re-frame.db        :refer [app-db]]))


(defn setup []
  (reset! app-db {}))

(defn teardown []
  (reset! app-db {}))

(defn each-fixture [f]
  (setup)
  (f)
  (teardown))

(use-fixtures :each each-fixture)

; register dummy handler with faulty middleware.
; undoable is a factory s.b. (undoable "description")
(re-frame.core/register-handler
  :with-faulty-middleware
  [trim-v undoable]
  (fn [db [arg]] (assoc db :event-value arg)))

;; ---------- TESTS ------------------------------------------------------------

(deftest test-faulty-middleware
  (testing "dispatch to handler with faulty middleware should not silently fail"
    (re-frame.core/dispatch-sync [:with-faulty-middleware "event value"])
    (is (= "event value" (:event-value @app-db)) "Handler failed to fire properly and set :event-value.")))

Is there anything wrong with subscriptions that subscribe to other subscriptions?

I know you shouldn't but subscriptions in handlers, and what to do instead. However, is the same true for putting subscriptions inside subscriptions? Here's a simplified example use case (assume that the intermediate stages represented by the different subscriptions are required by different parts of the view, such as a label showing the current filter, a set of pagination link etc):

(register-sub
  :data
  (fn [db _] (reaction (:data @db))))

(register-sub
  :sort-order
  (fn [db _] (reaction (:sort-order @db))))

(register-sub
  :filter
  (fn [db _] (reaction (:filter @db))))

(register-sub
  :table-data
  (fn [db _] (let [data        (reaction @(subscribe [:data]))
                   type-filter (reaction @(subscribe [:filter]))
                   sort-order  (reaction @(subscribe [:sort-order]))]
    (reaction (sort-by @sort-order (filter #(= (:type %) @filter) @data)))))

(register-sub
  :table-data-page
  (fn [db _] (let [table-data (reaction @(subscribe [:table-data]))]
    (reaction (take 10 @table-data))))

My gut feeling was that it should be ok to have subscriptions subscribe to other subscriptions in this way, as it's akin to chaining reactions - however the way I'm doing it at the moment definitely looks like a bit of a hack.

Navigation Best Practices

I have been thinking about what's the best way to structure the app map so that it can deal with navigation.

You can dispatch navigation events like :section/navigation, or :section.subsection/navigation

Seems like you need to have a navigation key in your app structure. For example {:x 1 :y 1 :active-nav :x}

That way top level views know what sections to display. You could do it for app data and views that are nested as well {:x {:a 1 :b :active-nav :b}} :y 5 :active-nav :x}

The other way might be something like {:x {:display :true} :y {:display :false}}

Though every time you wrote a navigation event handler, you'd have to have one that caused all the displays to be false, and then set the active one. Would be a pain if you keep adding sections.

Chained Reactions Macro

Hey Mike, just wanted to get your thoughts on a macro I thought might be useful before making a PR. The idea is that it's going to be a common operation to have a chained list of reactions that each depend on eachother and it would be useful to have a macro to encapsulate this idea. For example, say app-db is {:a {:b {:c 1}}}. A likely subscription might be

(let [a-reaction (reaction (:a @app-db))
      b-reaction (reaction (:b @a-reaction))
      c-reaction (reaction (:c @b-reaction))]
      (do-something-with c-reaction))

In this case the reactions all used simple key functions but you may want a chain of reactions that has more interesting functions. Either way, I wrote a macro that can be used as follows to do the above:

(with-chained-reaction [c-reaction [:a :b :c] app-db]
      (do-something-with c-reaction))

You pass in a vector with the name of the final ratom, a set of functions to be used in sequence, and your app-db along with any code you want to execute in that context.

The relevant code is here:

(defn create-let-bindings
  [previous-ratom functions]
  (if-let [function (first functions)]
    (let [new-ratom (gensym)
          previous-ratom-application `@~previous-ratom
          first-let-binding [new-ratom (list 'reaction (list function previous-ratom-application))]]
      (if (fnext functions)
        (concat first-let-binding
                  (create-let-bindings new-ratom (rest functions)))
        first-let-binding))
    ()))

(defmacro with-chained-reaction
  [[result functions db] & rest]
  (let [let-bindings (into [] (create-let-bindings db functions))]
    `(let ~let-bindings
       (let [~result (get ~let-bindings (- (count ~let-bindings) 2))]
         ~@rest))))

Is this something you might be interested in?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.