day8 / re-frame Goto Github PK
View Code? Open in Web Editor NEWA ClojureScript framework for building user interfaces, leveraging React
Home Page: http://day8.github.io/re-frame/
License: MIT License
A ClojureScript framework for building user interfaces, leveraging React
Home Page: http://day8.github.io/re-frame/
License: MIT License
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?
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.
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).
Something like this perhaps:
https://gist.github.com/mike-thompson-day8/76812d5452747bc79aac
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!
I'm not sure what options there are in ClojureScript, but in Clojure Zach Tellman has Potemkin with import-vars. From clj-commons/potemkin#31 it doesn't look like Potemkin supports this in cljs.
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'))))
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
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.
If I make a change to a sub, I have to reload the page or otherwise change the state in order for the sub to get triggered. Ideally, the subs should fire if they're reloaded.
In the section 'CPU Hog Problem', the link should be to
https://github.com/Day8/re-frame/wiki/Solve-the-CPU-hog-problem
instead of to
https://github.com/Day8/re-frame/wiki/The-CPU-Hog-Problem
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"}])}))
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?
Should we consider converting this to a re-frame example:
https://github.com/vvvvalvalval/reagent-phonecat
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:
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?
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)))
The release notes are currently as follows ... what else? Code is in develop
branch ...
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).apply-event
middleware and replace with similar trim-v
register-subs
to register-sub
(avoid confusion over possible plurals)set-max-undos
to set-max-undos!
undoable
middleware is now a factory. Where before you used this undoable
, you must now use this (undoable "some explanation")
. See further below.debug
, enrich
and after
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").
In todomvc, to be done tomorrow ahead of release:
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.
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?
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.
@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):
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.
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
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.
Making relatively trivial setters and getters requires 4 forms:
(defn set-thing [db _])
(defn get-thing [db _])
(register-handler :set-thing trim-v set-thing)
(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.
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?
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?
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?
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])
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.
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:
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
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:
Ie. the changes in step 2 are lost. (no longer in app-db)
Hey @mike-thompson-day8,
You might be interested in the explanation/description of middleware at https://github.com/boot-clj/boot/wiki/Tasks. I thought the picture was pretty good. I saw the middleware wiki page has quite a bit of effort put into it, and there diagram might be helpful.
Cheers,
~Earl
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?
https://github.com/Day8/re-frame/wiki/The-CPU-Hog-Problem#forcing-a-one-off-render
Is this the right approach? Perhaps the ^:flush-dom
metadata should be on the handler instead of the event?
The current approach requires that the dispatcher of an event knows about the implementation details of the handler. That's not good.
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?
$ 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.
You could define handle-subscribe
and a handle-dispatch
multimethods and have no need to manually register handlers. Does this sound like a good idea to you?
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.
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:
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!
Hi,
I wonder how i can use datascript with re-frame ?
Is there an example somewhere ?
Many thanks,
Samuel
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.")))
Create this example
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
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.
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.
Would it be possible to start a re-frame cookbook, similar to the ones for reagent and om?
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?
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.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.