Clojurians
#re-frame
<
2016-03-08
>

This page is not created by, affiliated with, or supported by Slack Technologies, Inc.

fasiha00:03:58

@danielcompton: ahaha, excellent, sorry, huge noob here. Thanks!

nidu05:03:15

@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.

danielcompton07:03:17

Yep, right on both counts

danielcompton07:03:56

There is a bit more that re-frame could do to make modular components, at the moment it's a bit tricky

nidu07:03:22

Do you plan some update?

mccraigmccraig10:03:40

@danielcompton: what are you thinking of re modular components ? my attempts to modularize are quite messy

roberto14:03:50

I like the components in keechma. I wish re-frame had something similar.

fasiha15:03:37

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

fasiha15:03:04

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

fasiha15:03:22

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.

fasiha15:03:06

That way the component could subscribe to this query at the beginning of its definition, with all the other queries

fasiha15:03:29

Is there a third option that's better that I'm overlooking?

mccraigmccraig16:03:48

@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

danielcompton17:03:02

@mccraigmccraig: no concrete thoughts yet

nidu17:03:26

@mccraigmccraig: is modular component a specific term or just a common description of a composable/reusable component?

firstclassfunc17:03:12

does anyone have any experience embedding a code-editor like ACE into re-frame/reagent?

mccraigmccraig17:03:59

@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

nidu17:03:26

@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?

mikethompson18:03:54

@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.

firstclassfunc18:03:18

@mikethompson: tks

mccraigmccraig18:03:13

@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...

mccraigmccraig18:03:34

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

mccraigmccraig18:03:45

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

mccraigmccraig18:03:53

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

fasiha18:03:46

@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!

hugobessaa18:03:53

@nidu @mccraigmccraig I use a wrapper component that passes simple props with data and with callbacks to a pure component.

hugobessaa18:03:03

but I have never had the need to abstract the wrapper itself

mccraigmccraig18:03:40

@hugobessaa: how do you express your callbacks ?

nidu18:03:01

@mccraigmccraig: your concern is that a lot of props passed between components?

hugobessaa18:03:06

real example:

hugobessaa18:03:38

I have a filters component, which displays sections of filters to be applied

hugobessaa19:03:28

It receives on-select as props

hugobessaa19:03:09

When the user clicks some filter checkbox, it calls the function on-select passed by it's parent

mccraigmccraig19:03:21

@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

hugobessaa19:03:34

I also have an user-filters component

hugobessaa19:03:39

that just passes the data and callback to filters

nidu19:03:08

@hugobessaa: sounds a lot like container/component pattern to me

hugobessaa19:03:02

but I'm almost hitting the need to create a dynamic subscription to hold data needed by some "container"

hugobessaa19:03:28

like you said

hugobessaa19:03:49

@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.

mikethompson19:03:00

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.

hugobessaa19:03:48

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.

hugobessaa19:03:27

Maybe handlers will be used for when you need to orchestrate many requests to different resources?

hugobessaa19:03:51

I will still use them to fire PUT and POST requests

mangr3n20:03:06

hey guys how would I dispatch an event based on a subscription value?

mangr3n20:03:53

(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)) )))

mangr3n20:03:48

Can I dispatch an event in the subscription safely? I might be overthinking this.

mangr3n20:03:33

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...

roberto20:03:54

I would probably have a subscription that does that only does that.

mangr3n20:03:06

Nevermind, the enrich middleware is the solution

mangr3n20:03:40

now I need a way of mixing different enrich middleware functions into my central app-db from around my application… ugh...

hugobessaa20:03:11

I think it would be better to just make these handlers validate the db

hugobessaa20:03:28

each input has it's handlers?

hugobessaa20:03:41

I'm not sure I understood your question

mangr3n20:03:11

No, I use a generic handler to update the db for all text/password fields. Why implement 15 handlers for the same basic behavior.

mangr3n20:03:56

I can use enrich to update the db if on my create account form the passwords don’t match

hugobessaa20:03:14

yeah. makes sense. you can enrich or simply call a function to get db errors and assoc them into the db

hugobessaa20:03:16

(-> db (assoc-new-text input-key) assoc-errors)

mangr3n20:03:19

Hmmm…. Trying to think about how to do this so I can repeat the pattern in other places

mangr3n20:03:31

yeah that’s the easy part I already have the handlers for that

mangr3n20:03:51

(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})

mangr3n20:03:29

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”)

mangr3n20:03:51

but I also want to set it up so I can add other functions to run through enrich as I expand the application

codonnell20:03:17

@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))?

mangr3n20:03:20

probably I’m still working on understanding all the different immutable data manipulation functions and how to use them with map/filter/reduce

codonnell20:03:17

I am also working on that, and I didn't mean that to come across as nitpicking. Just trying to learn, myself.

mangr3n20:03:57

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.

mangr3n20:03:58

Damn, can enrich run functions that are stored in the db?

mangr3n20:03:39

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...

mangr3n21:03:32

I just hate putting functions (and reagent components) in the db, they muddy up the debug output with a lot of useless noise.

codonnell21:03:21

Is there a reason you need to add and remove them, rather than just leave the handlers?

codonnell21:03:25

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.

codonnell21:03:11

That enrich middleware will update your db with new error data based on the results of (validate-form form-data).

mikethompson21:03:20

@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.

mikethompson21:03:04

Just to be clear: the enrich middleware recalculates the warnings and errors state (from scratch each time) and puts it into app-db.

mikethompson21:03:11

@mangr3n: never put functions in app-db. If you have to do that, somethng is wrong.

mikethompson21:03:06

You want your event handler (together with any middleware it might have, like enrich) to be a pure function.

mangr3n21:03:42

hmm… I’m thinking if I don’t have the password form open, why perform the calculation on the database?

mikethompson21:03:17

You put the enrich middleware only on event handlers which can perform a triggering mutation

mangr3n21:03:39

yeah but I can wrap all event handlers with the same middleware

mangr3n21:03:52

through a helper function then I don’t have to think about it

mangr3n22:03:36

why not put functions in app-db? functions are data...

mccraigmccraig22:03:32

@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

mangr3n22:03:01

yeah, I was just thinking through the stateful implications.

mangr3n22:03:19

hmm… and the reference lookups are extremely fast.

mccraigmccraig22:03:07

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

mangr3n22:03:29

I get it, I’m trying to think about how to do it differently.

mangr3n22:03:24

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

mangr3n22:03:14

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.

mccraigmccraig22:03:40

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 ?

mangr3n22:03:49

type 1 component

mangr3n22:03:09

[<func-name-which-returns-hiccup>]

mccraigmccraig22:03:24

why not put just a key which can be turned into the component function somewhere else - by a case or multimethod or whatever ?

mangr3n22:03:34

yeah, that’s the next step I’m realizing

mangr3n22:03:41

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?)

mangr3n22:03:04

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.

mccraigmccraig22:03:40

a multimethod allows you to add new methods without changing existing code

mangr3n22:03:17

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?

mangr3n22:03:22

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)

mangr3n22:03:36

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….

mangr3n22:03:04

wow… I can’t even explain the rippling restructuring that’s happening in my mind right now...

mccraigmccraig22:03:26

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

mccraigmccraig22:03:33

and enjoy the cognitive reconstruction :simple_smile:

mangr3n22:03:09

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.

mangr3n22:03:52

I’m going full stack Datomic > Clojure > REST > ClojureScript > Re-frame

mangr3n23:03:13

Had to prove the value and flexibility on the front end and a strategy for bringing it in.

mccraigmccraig23:03:41

i went a similar way, though i'm on cassandra rather than datomic - certainly haven't regretted it