Fork me on GitHub
#re-frame
<
2018-03-19
>
genRaiy13:03:28

@mikethompson I wrote an effect handler in a few lines after following the manual the old fashioned way and it all works fine

lepistane14:03:11

Could someone have a look into https://github.com/StankovicMarko/invoices I feel like i am not using re-frame correctly to me it feels like reagent and it's atoms on steroids. To me it feels like i am using re-frame db as reagent.session is this how it is supposed to be? also is this good approach? https://github.com/StankovicMarko/invoices/blob/master/src/cljs/invoices/components/login.cljs#L25 for some reason i am thinking this is not best practice and that i should be using something like this https://github.com/Day8/re-frame/blob/master/docs/Talking-To-Servers.md (dispatch reg-event-fx which will send request and describe next step, which dispatches reg-event-db and update db) any feedback would be greatly appreciated

manuel16:03:56

Hi, I have a question about event dispatching. I need to initialize my app-db with external data, so I am doing something like this:

(rf/reg-event-fx
 ::initialize-db
 (fn [_ _]
     {:dispatch [::my/event]
     :db default-db}))
The dispatch goes well, but it takes a while because there is quite a lot of data to be loaded. The problem is when I need to access the data in app-db from another event. ::my/event loads the data correctly (I inspected app-db via re-frame-10x), but while it's loading it the other events proceed and don't have access to the data ::my/event have yet to load. Ideas?

lepistane16:03:11

@manuel hi, i am not re-frame guru i am learning it myself but i saw something like dispatch-sync which basically says oke do this now but there was a note that you shouldn't do this unless you know exactly what you need so what i propose is (rf/dispatch-sync [::initialize-db]) instead of regular dispatch. can't find this in re-frame docs right now but try looking for it

joshkh16:03:41

@manuel is that data being loaded being fetched asynchronously? perhaps from some ajax call somewhere?

manuel16:03:07

@lepistane dispatch-sync docstring says to avoid using it in an event handler, though, that's why I wasn't rely on it @joshkh yes, via ajax

manutter5116:03:12

@lepistane @manuel You probably do want dispatch-sync for this case, but you need to use it on [::my/event], not [::initialize-db]. Otherwise ::initialize-db will go off, dispatch ::my/event and then return before ::my/event runs.

manutter5116:03:24

I think it's ok to use dispatch-sync in the initialize-db because that's a special kind of event -- you need dispatch-sync for exactly this kind of situation

manuel16:03:06

ok, thanks guys, I'll play around with dispatch-sync and see what I can do. 🙂

lepistane16:03:23

tnx for clarification, i was close then good luck manuel

manuel16:03:15

oh wait, I am already doing this: (rf/dispatch-sync [::db/initialize-db])

lepistane16:03:48

(rf/dispatch-sync [::my/event]) as it was pointed by manutter51

manuel16:03:07

trying it right now

manuel16:03:29

but it is still taking too much time

joshkh16:03:11

i'm sure it's all good (i don't know much about dispatch-sync) but i kind of feel like it might be an IO / race condition.

joshkh16:03:32

if dispatch-sync was going to solve your problem then it shouldn't matter how much time it takes, right?

joshkh16:03:07

@manuel any chance your code is online somewhere? 🙂

manuel16:03:50

can't share the code unfortunately

joshkh16:03:59

if you're directly seeding the initial db from an ajax call then it's not going to work

manutter5116:03:05

@manuel right, you do not want (rf/dispatch-sync [::db/initialize-db]), because that will wait for the initialize step to finish

joshkh16:03:45

sorry, i meant to say that it can totally work but if depends on you're handling it

manutter5116:03:46

The problem is that the initialize step is calling my/event asynchronously, so initialize-db returns immediately, before my/event completes.

manutter5116:03:46

Actually, you probably want dispatch-sync on both ::db/initialize-db AND ::my/event, to make sure none of them return before your data is loaded.

manuel16:03:28

@manutter51 thanks, I'll try that

lepistane16:03:25

Could someone have a look into https://github.com/StankovicMarko/invoices ? I feel like i am not using re-frame correctly to me it feels like reagent and it's atoms on steroids. To me it feels like i am using re-frame db as reagent.session is this how it is supposed to be? also is this good approach? https://github.com/StankovicMarko/invoices/blob/master/src/cljs/invoices/components/login.cljs#L25 for some reason i am thinking this is not best practice and that i should be using something like this https://github.com/Day8/re-frame/blob/master/docs/Talking-To-Servers.md (dispatch reg-event-fx which will send request and describe next step, which dispatches reg-event-db and update db) any feedback would be greatly appreciated

manuel16:03:44

got this error in the browser console, though: dispatch-sync was called for [::my/event] . You can't call dispatch-sync within an event handler

manuel16:03:00

oh yes, as I was saying before, the dostring is pretty clear about not using it in an event handler 🙂

joshkh16:03:20

@manuel does [::my/event] kick off some press to data fetching process, or are you saying it can't see data that should be in default-db?

manuel16:03:13

[::my/event] is basically only an ajax call to fetch data which, upon success, are loaded into default-db

joshkh16:03:44

i'm sure manutter51 is onto something. i'm still a little worried about that ajax call. do you want the ajax call to stores its data before any other events happen beyond :initialize-db?

joshkh16:03:19

okay, so i'm doing something similar on another application that fetches loads of assets, and i don't want the user clicking around and mucking things up until they're all settled. i do so by firing this event with dispatch-sync https://github.com/intermine/bluegenes/blob/dev/src/cljs/bluegenes/events/boot.cljs#L115

joshkh16:03:32

that's an overly complex example though

joshkh16:03:33

day8 has a handy effect for handling async flow: https://github.com/Day8/re-frame-async-flow-fx

joshkh16:03:28

but for instance, i don't route to any of the app's sections until i've seen a :finished-loading-assets event

manuel16:03:15

thanks for the tip, I'll try the day8 effect

joshkh16:03:11

again, that might be over kill! your :my/event fires an ajax request. but what's triggering the third event that happens to early too see the response?

manuel16:03:28

another ajax request

joshkh16:03:14

maybe you can have my/event fire an ajax request, and that response handler fires the next ajax request?

joshkh16:03:37

it sounds like they shouldn't be running in parallel if the second depends on the results of the first

manuel16:03:38

you mean using re-frame-async-flow-fx?

manuel16:03:01

yes, they don't have to run in parallel

manuel16:03:52

oh, wait, I see what you mean. No need for re-frame-async-flow-fx then.

manuel16:03:58

just normal event chaining

joshkh16:03:08

so something like this?

joshkh16:03:11

(reg-event-fx
  ::my-event-1
  (fn [world [_]]
    {:my-ajax-effect {:url "/items1"
                      :on-success [::store-event-1-results]}}))

(reg-event-fx
  ::store-event-1-results
  (fn [{db :db} [_ response]]
    {:db (assoc db :results-1 response)
     :dispatch [::my-event-2]}))

(reg-event-fx
  ::my-event-2
  (fn [world [_]]
    {:my-ajax-effect {:url "/items2"
                      :on-success [::store-event-2-results]}}))

(reg-event-db
  ::store-event-2-results
  (fn [db [_ response]]
    (assoc db :results-2 response)))

manuel16:03:15

I can try that, although ::my/event is currently used elsewhere too

joshkh16:03:57

my-event-1 fires the ajax effect, and when it finished it calls store-event-1-results which updates the db and then dispatches the second ajax event, my-event-2

joshkh16:03:26

my-event-2 will see the updates in app-db from the previous event 🙂

manuel16:03:37

yeah, a clean solution. Thanks @joshkh. I'm leaving the office now, but I'll try your solution first thing tomorrow.

joshkh16:03:48

good luck!

lwhorton21:03:51

i was wondering if someone could talk with me about a good strategy for structuring an app, mechanically, that’s designed for growth and isolation. specifically, how do you handle updates to data on a server via the event-handler-server-handler loop, and how do you structure queries to optimize reuse and decoupling?

lwhorton21:03:50

say for example I have a user-profile page… the data’s easy enough, right? name surname DOB [some other domain-specific account stuff]. generically I could create a user-profile feature that would house and handle all of the user-related data updates. by storing the user data flatly in the db, anyone else could subscribe to this data, and all’s good in the world.

lwhorton21:03:23

but what if some new feature enables you to change your name, or maybe your address, or some other user-entity-related data WITHOUT going through the existing user-profile page?

lwhorton21:03:15

all the handlers that exist for updating data are in some other feature, and you wouldn’t want to emit events from :some-new-feature that target :user-profile/address-changed.. that would be almost like programming to an implementation instead of an interface.

lwhorton21:03:00

do you extract out those handlers to a more generic “user entity” set of handlers? then both the user-profile and some-new-feature features would essentially be emitting events to “somewhere else” that handles the data.

lwhorton21:03:27

in this sense it’s like the opposite of a “shared common query”-- reusing queries is encouraged and helpful, but what about reusing mutating handlers? I think it’s kind of corollary to how people generally go through some interface to access a db-- or at the very least a set of common functions, so that you avoid a ton of db-connections littered everywhere throughout code

lwhorton21:03:47

@ericnormand you have some great content on re-frame; maybe this could be another subject to tackle in a future post?

ericnormand21:03:32

it's a great question

ericnormand21:03:46

I think it basically boils down to how you model your domain

ericnormand22:03:11

your events and subscriptions should be semantic definition of your domain actions/entities

ericnormand22:03:10

so if your user can change their name, and that action makes sense in itself, then it's an action you should name

ericnormand22:03:35

I believe that tying it to a certain view in your app is exactly where people go wrong

ericnormand22:03:20

they're often afraid of the small level of indirection, thinking "change name" is too abstract, that it should be tied to something concrete, so they tie it to the particular page on the site

ericnormand22:03:31

that makes it hard to grow or to reuse that action

ericnormand22:03:47

but effectively modeling a domain is a hard problem

ericnormand22:03:58

"what is a person's name?", etc

ericnormand22:03:05

it's not clear

ericnormand22:03:12

requires some UX work

ericnormand22:03:23

and that's where people get lazy

ericnormand22:03:45

you need to be looking for the timeless semantics of your app

ericnormand22:03:56

like "people have names, and they're strings"

ericnormand22:03:04

:person/name :string

ericnormand22:03:40

but if it's "user accounts have names, that may or may not be their given person name", then it's different

ericnormand22:03:16

but I'd definitely never tie it to the profile page, as in :profile-page/change-name

ericnormand22:03:19

or anything like that

lwhorton22:03:25

that’s a really interesting take on the problem, almost top-down and not bottom up-- everything that affects your domain is a domain-event and, only if necessary, scope other events that are feature-specific (read: not of domain importance) to a finer-grained event.

ericnormand22:03:48

yeah, that's a good way to put it

lwhorton22:03:09

you could more-easily structure your code (again, mechanically) to be able to see “what are all the thingies that change my user domain model”, too

lwhorton22:03:33

as always, +1 for the nor-man

ericnormand22:03:26

>Event and Subscription Naming Summary > >Name so you can reconstruct what happened later >Names should reflect the domain concepts not the technical concepts >Events names should capture the intent, not their effects and not the UI action >Subscription names should describe the data, not the implementation and not the >Component

lwhorton22:03:36

and btw; your new apropos is great. i love the casual-ness, but most importantly the REPL time. it’s a really great idea to watch clojure experts think their way through different problems. you get to see some neat lang features (`cycle`, juxt) and it’s all very relatable.

lwhorton22:03:43

i would watch you guys all day if it were structured like: 10 minutes of new clj developments (releases, libs, news, etc.) followed by 20ish minutes of solving a problem on a repl.

ericnormand22:03:42

yeah, 30 minutes of conversation feels like it's not enough time for 4 people to say anything

ericnormand22:03:52

maybe we could structure it more so it's just updates

ericnormand22:03:04

we've heard people mention they like the repl segment better

ericnormand22:03:10

maybe we'll evolve that way

lwhorton22:03:01

while it would be great to just “have a conversation” for a long while, i think the casual nature works against this objective… it doesn’t seem like there’s a lot of preparation going into what you want to say, so the conversation about something new is fairly shallow, if that makes sense

ericnormand22:03:51

I think also we need to work on the notes/topic selection

ericnormand22:03:55

the last one was pretty bad

lwhorton22:03:19

hah. i haven’t watched #3 yet

ericnormand22:03:26

someone added them but the notes weren't descriptive enough to know what they were about

ericnormand22:03:05

i'd like a rule like "at least 2 of us have to have something to say about it"

ericnormand22:03:19

or just know that you're on your own talking about it

lwhorton22:03:42

that’s probably more fair to the panel-- they only spend time preparing for 1-2 topics instead of 3-5

ericnormand22:03:30

it has to be important to someone!

ericnormand22:03:42

maybe "strong feelings" should be the criterion

lwhorton22:03:03

oh, you mean the panelists pick the topics? or you propose topics and would axe anything that doesn’t garner interest?

lwhorton22:03:00

it would also be cool for the “problem of the week” to be something pulled directly out of a workday (and trimmed down appropriately), as opposed to a random “solve the fib sequence” type of problem

ericnormand22:03:19

there needs to be at least two people with strong feelings about the topic or it won't be interesting to hear the panel talk about it

ericnormand22:03:53

yeah, those problems are harder to find

ericnormand22:03:09

and we've been having a lot of discussion around the simplest whiteboard interview style problems

ericnormand22:03:17

I think they're good for being small

ericnormand22:03:29

you can see multiple implementations in the same episode

lwhorton22:03:28

good point. its probably fanciful to think you can extract a context free problem that’s solvable in under a few minutes on the regular.

ericnormand23:03:55

thanks for the discussion