This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2016-03-08
Channels
- # admin-announcements (3)
- # alda (2)
- # beginners (66)
- # boot (54)
- # cider (21)
- # clara (1)
- # cljsfiddle (32)
- # cljsrn (9)
- # clojars (4)
- # clojure (164)
- # clojure-dusseldorf (4)
- # clojure-japan (2)
- # clojure-norway (1)
- # clojure-russia (76)
- # clojure-sg (8)
- # clojurescript (19)
- # core-async (1)
- # core-typed (1)
- # cursive (6)
- # datomic (1)
- # editors (48)
- # hoplon (20)
- # immutant (2)
- # jobs-discuss (6)
- # ldnclj (1)
- # om (82)
- # onyx (6)
- # parinfer (11)
- # proton (2)
- # re-frame (113)
- # reagent (17)
- # testing (11)
- # untangled (11)
- # vim (4)
- # yada (38)
@danielcompton: ahaha, excellent, sorry, huge noob here. Thanks!
@danielcompton: sorry, one more note. Doesn't your approach (here https://clojurians.slack.com/archives/re-frame/p1457374918000720) with keeping selection in app-db
make component less reusable? If you want to maintain several instances of this component - you have to assign each instance some key to store it's state under? And btw approach that i mentioned (here https://clojurians.slack.com/files/nidu/F0QSBDVFZ/Untitled.clj, with setting key
) has a major drawback - it disallows smooth transition.
Yep, right on both counts
There is a bit more that re-frame could do to make modular components, at the moment it's a bit tricky
@danielcompton: what are you thinking of re modular components ? my attempts to modularize are quite messy
I'm following the recommendations in https://github.com/Day8/re-frame/wiki/Subscribing-To-A-Database, using a subscription to talk to a JSON-serving endpoint and a handler to deal with the returned data by porting the Elm zip code example (http://elm-lang.org/examples/zip-codes). I have a subscription that makes a GET request and with a callback that dispatches an event to store the response. So I have a simple handler on the other side of that event to save the response to the db. Finally, I have a second subscription that looks at the JSON response (now in the db) and extracts the interesting information from it. Uh, maybe it'll help to see the sources: https://github.com/fasiha/zip-code-re-frame/tree/master/src/cljs/zip_code_reframe
My question is who should subscribe to the query that sends off the GET request? Right now, I'm doing something that I think is for sure wrong: after validating the input, deep inside my component, I fire off a re-frame/subscribe
I could move the validation inside the subscription that makes the GET request (here called :look-up-zip-code
), so that the GET request is only sent if the input makes sense.
That way the component could subscribe to this query at the beginning of its definition, with all the other queries
@fasiha: i may be taking a non-idiomatic approach, but i ended up doing it differently and sending API requests from a handler, with the events getting dispatched during app init, sometimes as part of a components lifecycle, and lots of times after user action in the UI. it's certainly easier to set "loading" indicators on your UI that way
@mccraigmccraig: no concrete thoughts yet
@mccraigmccraig: is modular component a specific term or just a common description of a composable/reusable component?
does anyone have any experience embedding a code-editor like ACE into re-frame/reagent?
@nidu in this case i'm meaning a component which requires state in the app-db
and can have multiple active instances in an app... so the component fn, handlers and subs all need to be parameterised somehow
@mccraigmccraig: Got it. I guess the best option would be to make these component independent of app-db
and use container/component pattern. Container can store it's entire state in app-db
. Container itself is a parameter in this case. Did you use another approach?
@fasiha: @mccraigmccraig that "Subscribing To A Database" is very much a work in progress. Not officially published yet. I can totally see why you'd do requests from a handler.
@firstclassfunc: codemirror / reagent done here: http://yogthos.net/posts/2015-11-12-ClojureScript-Eval.html
@nidu: i use container-component a lot... but in most of those cases the contained components (mostly) get their state as props rather than subs...
i've got one particular case where the interactions are more complicated... there's a scroll-handling component, an infinite-list container component and the component which brings them all together and supplies the query and presentation functions which are used to retrieve data and render it
i ended up passing paths into app-db
around, along with "partial" events - vectors with [:event-kw & args]
which get more args added to the end before dispatch
it works fine, and i keep to the only-simple-data-in-events rule, but i can't help feeling there is a better way
@mccraigmccraig: ah, it is a great idea to put the request in the handler for keyboard entry events in this case. Validation happens right there, and I just have a simple subscription for its response. ¡thanks!
@nidu @mccraigmccraig I use a wrapper component that passes simple props with data and with callbacks to a pure component.
but I have never had the need to abstract the wrapper itself
@hugobessaa: how do you express your callbacks ?
@mccraigmccraig: your concern is that a lot of props passed between components?
real example:
I have a filters
component, which displays sections of filters to be applied
It receives on-select
as props
When the user clicks some filter checkbox, it calls the function on-select
passed by it's parent
@nidu: no, not really - it just feels like i have a half-baked abstraction, with contributions required from many places which aren't co-located, and so can be confusing to keep track of... it might just be me taking a bit of time to get used to it
I also have an user-filters
component
that just passes the data and callback to filters
@hugobessaa: sounds a lot like container/component pattern to me
but I'm almost hitting the need to create a dynamic subscription to hold data needed by some "container"
like you said
@mikethompson: I really like the idea described at "Subscribing To A Database". It will help to avoid using too much :component-did-mount
to fetch resources.
I like the idea in it too. But i suspect it is not the only way. I think the pattern where an event handler loads the data will have its own use cases.
I'm using the event handler pattern for now. But even user events can just change some app-db key and a subscription would re-fetch it.
Maybe handlers will be used for when you need to orchestrate many requests to different resources?
I will still use them to fire PUT
and POST
requests
(re-frame/register-sub :create-passwords-match (fn [db [_]] (reaction (let [passwd (-> @db :form-fields :create-form-password) passwd-alt (-> @db :form-fields :create-form-password-alt)] (= passwd passwd-alt)) )))
This isn’t working, any thoughts? I want to trigger a handler that registers error messages for form fields. The ui will pick up the errors and render, but if those two fields don’t match, I want a handler to fire that tells the user that the passwords don’t match, by rendering an error message for rendering on the input field. I’d prefer to leave my generic input-handler alone, and react specifically to these two specific values (subscription). Rather than creating a custom input handler for those two fields...
now I need a way of mixing different enrich middleware functions into my central app-db from around my application… ugh...
I think it would be better to just make these handlers validate
the db
each input has it's handlers?
I'm not sure I understood your question
No, I use a generic handler to update the db for all text/password fields. Why implement 15 handlers for the same basic behavior.
I can use enrich to update the db if on my create account form the passwords don’t match
yeah. makes sense. you can enrich or simply call a function to get db errors and assoc
them into the db
(-> db (assoc-new-text input-key) assoc-errors)
Hmmm…. Trying to think about how to do this so I can repeat the pattern in other places
(defn register-error-message [db [key message]] (assoc-in db [:errors key] message)) (defn clear-error-message [db [key]] (let [errors (:errors db)] (assoc db :errors (dissoc errors key)))) (defn clear-form-errors [db [form-name]] (let [fields (map :id (-> hylng.login.views/forms form-name :fields)) errors (reduce dissoc (:errors db) fields)] (assoc db :errors errors))) (register-handler-map {:register-error register-error-message :clear-error clear-error-message :clear-form-errors clear-form-errors})
so I want to “enrich” so that If the (not (= (-> db :form-values :password) (-> db :form-values :password-alt))) (dispatch :register-error :create-form-password “Passwords don’t match”)
but I also want to set it up so I can add other functions to run through enrich as I expand the application
@mangr3n: I'm not sure familiar with re-frame, but could you replace your clear-error-message
function with (defn clear-error-message [db [key]] (update db :errors dissoc key))
?
probably I’m still working on understanding all the different immutable data manipulation functions and how to use them with map/filter/reduce
I am also working on that, and I didn't mean that to come across as nitpicking. Just trying to learn, myself.
I appreciate it, don’t get me wrong. I come from 20 years of OO/imperative work. I didn’t even realize I was often creating pure functions in order to save myself headaches. I’ve been dabbling (more like thinking about) using clj/cljs for about 2 years now, and I’m finally using it in a work project. So, I’m on the front end of the same learning curve.
Why not just register functions in the db when the form opens, and remove them when it’s done? Have enrich look for a vector and execute the registered functions...
I just hate putting functions (and reagent components) in the db, they muddy up the debug output with a lot of useless noise.
Is there a reason you need to add and remove them, rather than just leave the handlers?
The way I see a solution to this problem working out in my head is you have a pure function validate-form
or something that takes form data as input and returns error data. You could add middleware that enriches whichever handlers you want to trigger validation.
That enrich middleware will update your db with new error data based on the results of (validate-form form-data)
.
@mangr3n: yep. That's the way we think about it. Event handlers mutate state and, as a result, that state may now have errors and warnings associated with it. So we use the enrich
middleware to recalculate errors and warnings, every single time, from scratch. This is a wonderful model to program. The errors and warnings are simply a function of the existing state. warnings = f(app-db)
So we never think "oh we had a warning X last time, maybe we should remove it now" ... instead we recalculate the entire set of errors and warnings each time based on what is currently in app-db
. After which the Views see the new warnings (or their absence) and respond with necessary DOM.
Just to be clear: the enrich
middleware recalculates the warnings and errors state (from scratch each time) and puts it into app-db
.
@mangr3n: never put functions in app-db
. If you have to do that, somethng is wrong.
You want your event handler (together with any middleware it might have, like enrich) to be a pure function.
hmm… I’m thinking if I don’t have the password form open, why perform the calculation on the database?
You put the enrich middleware only on event handlers which can perform a triggering mutation
Sure.
@mangr3n: fns are first-class, but they are opaque - you can't see what they have hidden away in their closures or know what the range of their values is for given args
even if they are pure, two identically behaving functions won't compare equal unless they are identical - understanding why your app is behaving as it is is much harder if the state contains functions
the way I implemented popup dialogs uses the app-db to store the content component and the on-close fn… for cleaning up fields and errors
and the title, which is in fact a simple data value, technically the content could be a data structure but we know that some of my components will in fact be functions.
when you say you are putting the "content component" in the app-db, what actually is the "content component" ? are you talking hiccup, a type 1,2 or 3 component function, or something else ?
why not put just a key which can be turned into the component function somewhere else - by a case or multimethod or whatever ?
I’m trying to rethink some of the same strategies I’d use in OO in a functional world. I used to create builders that would take maps all the time, in order to generate objects (which often were effectively just other data structures) but I never thought of it that way. My application in development is mutating, and I keep thinking about the pieces I’m building not as one off’s but as first of N’s sometimes it’s overkill, but I’m reaching for re-frame tools instead of language tools (multi-methods, types, otherstuff?)
hmmm… I need to really think about this, there’s a cognitive dissonance between what I’m trying to do, and the tools… a type wouldn’t work either. I still end up having a reference that has to be dereferenced to an instance of code, and I want to support stuff that doesn’t already exist yet, but not have to alter a map or multimethod when I create the next dialog.
a multimethod allows you to add new methods without changing existing code
let me look at that again, I thought it was all defined in one place… can I add to an existing multimethod in a different location?
oh, duh, that’s what I need, that would solve 90% of my cases. Just looked and realized that each expression is a distinct form…. doh! (I knew I wasn’t getting all of what Rich Hickey meant by polymorphism a la carte)
holy cow, see I know there are probably another 30-40 ways I’ve programmed that are going to be re-configured into something more efficient/functional once I understand it and map it onto tricks/problems I’ve faced in OO/imp land….
wow… I can’t even explain the rippling restructuring that’s happening in my mind right now...
i haven't used multimethods with re-frame, so i've not encountered this problem, but if you hit any difficulties you might want to read this. read it anyway, the solution @mikethompson suggests comes in handy http://stackoverflow.com/questions/33299746/why-are-multi-methods-not-working-as-functions-for-reagent-re-frame
and enjoy the cognitive reconstruction
no kidding, it’s one thing to know it’s coming, but not be sure how. It’s another to have it happen. I’m just glad I have the authority where I am working to dictate the implementation we’re moving to.
Had to prove the value and flexibility on the front end and a strategy for bringing it in.
i went a similar way, though i'm on cassandra rather than datomic - certainly haven't regretted it