Fork me on GitHub
#re-frame
<
2018-06-18
>
Oliver George01:06:43

@bravilogy it's been an age since I looked at secretary but my guess is the goog.History example in the readme might help guide you as to how that should all come together.

Oliver George01:06:11

It seems like the process is: 1. change uri 2. navigation event triggered, dispatch! called... secretary route invoked.

Oliver George01:06:42

Funny that they don't give an example of setting the browser uri programatically to start the process. Something like (.setToken h uri) should do the trick.

manutter5112:06:27

At the risk of posting a bleary-eyed, under-caffeinated Monday morning muddle, I’ve got a style question. Consider the following 2 components:

(defn component-1
  []
  (let [foo-sub (rf/subscribe [:my-foo])]
    (fn []
      [:div "My foo"
       [:div @foo]])))

(defn component-2
  []
  [:div "My bar"
   [:div @(rf/subscribe [:my-bar])]])
I’ve always written the former, but it seems like the latter is a lot more concise, and I think it’s pretty common that a subscription is only used once in a component. Thoughts?

mikethompson12:06:41

Nothing wrong with either ... but component-1 is slightly old-school these days and I'd be tempted to use a slight variation on the 2nd. First I'd consider using "Lambda Island Naming": https://github.com/Day8/re-frame/blob/master/docs/SubscriptionsCleanup.md#lambdaisland-naming--lin Then

(defn component-2
  []
  (let [meaningful-name (<sub [:my-bar])]
    [:div "My bar"
      [:div meaningful-name]])

mikethompson12:06:29

Ie. 1. I have a preference for using LIN because it removes the possibility of forgetting the @ (which gets everyone sooner or later) 2. I kinda like having the subscription in a let to make is clearer. But that's just me.

manutter5112:06:08

Sounds good, thanks.

Hukka13:06:29

I'd suggest avoiding lets if the subscription keyword is clearly named. Of course in a function that small it's pretty obvious what's happening in any way, but generally everytime I see a let I start thinking "Ok, where are the other places this binding is referred to"

ScArcher14:06:36

Does anyone have time to help a clojure / re-frame beginner understand how to tie changes on the web page back to the database?

ScArcher14:06:30

Also I need a bit of help understanding how large / small a view should be. Right now i have one large main-panel function 🙂

ScArcher14:06:53

So far I have the page rendering and data is bound from the database to the page so that I see what I expect. I just don't know how to have changes on the page update the database so it can be reflected by the other subscriptions.

curlyfry14:06:13

@scott.archer @manutter51 I think this is a question about the local app-db rather than anything that requires http calls?

ScArcher14:06:52

@curlyfry Yes, the local app db, not using anything http.

manutter5114:06:06

Ah, I mis-read it, sorry @scott.archer

ScArcher14:06:17

It's a single page app with one view that just generates a few URLs and shows them on the screen based on the form input.

manutter5114:06:23

Ok, that’s what the reg-event-db stuff is for

ScArcher14:06:42

Right i'm just so new to both clojure and re-frame I'm having a hard time with the examples / documentation.

manutter5114:06:40

You use reg-event-db to register an event handler, which is a function that takes the current state of the database, and a vector of arguments, and returns the new/updated state of the database, usually via assoc or assoc-in

ScArcher14:06:49

I just want to have a form field, update a location in the database so that other areas subscribed see that data.

manutter5114:06:43

Ok, do you have a specific field you’re working with right now? Might be easier to use examples that map directly to what you’re currently working on

ScArcher14:06:10

My DB has a [:helpdesk [:incidnet-number "12345" :owner "joe"]]

ScArcher14:06:25

I want to type in incident number and have it updated.

ScArcher14:06:35

(type into an :input)

ScArcher14:06:18

those should have been maps, not vectors.

ScArcher14:06:17

But it looks like I need to write an event like you said reg-event-db

manutter5114:06:07

Right. The basic idea is that all your GUI front-end stuff ever does is to fire events, and then the event handlers worry about what to do with it

manutter5114:06:37

So for your input field you’ll just define an on-change handler that does

#(re-frame.core/dispatch [:helpdesk/update-incident-number (-> % .-target .-value)])

manutter5114:06:27

The last part of that vector is just pulling the current value of the text field out of the JS event.

manutter5114:06:23

Also, I like to use namespaced event names to organize my events, so all the events that have to do with the helpdesk form, I’ll call :helpdesk/whatever

ScArcher14:06:47

ok, so then I define a reg-event-db with :helpdesk/update-incident-number

manutter5114:06:22

Right, and your event handler will define a function that looks something like this:

manutter5114:06:42

(re-frame.core/reg-event-db
:helpdesk/update-incident-number
(fn [db [_ new-incident-number]]
  (assoc-in db [:helpdesk :incident-number] new-incident-number))

manutter5114:06:39

The first argument your handler function will get is the current value of the app-db

manutter5115:06:20

The second argument is the event vector that you passed to re-frame.core/dispatch. We use underscore for the first value inside that vector because we already know it’s going to be :helpdesk/update-incident-number. That leaves the second value as the one we’re interested in, and it’s the new value for the incident number.

tord15:06:53

Is there any consensus about when – if ever – to use plain old Reagent atoms in re-frame apps? I started out using atoms for all sorts of short-lived local state, like dialog contents and animation state, but I've gradually changed to using the app-db for everything. This somehow feels cleaner and makes tools like re-frame-10x more powerful, but having to mess with the app-db, events and subscriptions for every little thing I add to some dialog is sometimes annoying.

lwhorton15:06:44

i use atoms pretty much for every non-complex / (doesnt span multiple views) forms

lwhorton15:06:59

when someone submits the form / on-blur triggers, then i update the reframe db

manutter5115:06:05

We were just talking about this at work. My experience has been that if you have a component that has a bit of state that (a) is of no interest to any other component or handler (b) is not part of any application data and (c) needs no validation or other processing, then a local ratom is fine. My other experience has been that it’s not uncommon to put state into a ratom and then later discover whoops, this other component/handler DOES need to access it. But it’s really easy to re-factor.

lwhorton15:06:20

☝️ perfect

lwhorton15:06:33

write your local ratom state as if it was a slice of the database, then refactoring in the future is really simple… for example use a single map instead of multiple ratoms

👍 8
manutter5115:06:46

My canonical example is I have a popup or drop-down or something that needs to track open/closed state. It’s fine to put that into a ratom (until you decide that you want it to automatically close when you click some other component 😉 )

kennytilton15:06:59

I sometimes look at these close calls from the standpoint of self-documentation, specifically that using the db for purely local state is like false advertising that the state is anything other than local. My2c.

kennytilton15:06:40

And yeah, if it turns out to be non-local, refactoring is easy.

gadfly36116:06:20

@tord personally, I put all state into app-db and don't use local reagent atoms. I feel like when bugs happen in local reagent atoms, it is easier for them to be less visible.

👍 4
Chris Bidler16:06:59

I have a newbie question about working with CIDER and re-frame in a brand-new SPA project

Chris Bidler16:06:03

My expected pattern of work in CIDER (with backend code) is to visit a file, C-c M-n to put the REPL into the file’s namespace, then C-c C-k to load the file, and C-c C-e to eval forms as I add them to my source file

Chris Bidler16:06:48

I have a project I’ve created with lein new re-frame ion-client +cider +aliases and I have used cider-jack-in-clojurescript to get a cljs REPL

Chris Bidler16:06:24

I can do stuff like (js/alert "yo") and see that work, and if I make changes to the “starter” app-db and reload the browser pointed at localhost:3449 I see those changes

Chris Bidler16:06:10

but if I try to “load” any namespace that requires another one of the project namespaces, I get an exception that the namespace doesn’t exist

Chris Bidler16:06:21

so for example in ion-client.events I (:require [ion-client.db :as db] ...) and when I C-c C-k that file I get a stacktrace that leads off namespace ion-client.db does not exist

Chris Bidler16:06:47

so clearly I’m doing something wrong here but I don’t know if it’s trying to C-c C-k at all in a Figwheel-y world, or if my REPL setup is wrong, or what. Does anyone read what I’ve written and immediately think “oh man, that poor fellow has misunderstood thing“? , by chance? 😇

danielglauser16:06:03

Hey Chris! You may be able to get your setup working the way you are trying. What I do that’s a little different is run lein figwheel or lein dev from the command line then from Emacs run cider-connect.

Chris Bidler16:06:56

ah, okay - I’ll try that

danielglauser16:06:21

I’m making sure that my recent project embeds an nREPL server…

Chris Bidler16:06:32

I’m always seduced by the cyberpunk hipness of *-jack-in but it seems common that you want to run your REPL outside and just connect to it. 🙂

danielglauser16:06:23

Yeah, it depends upon what you want. For most of my workflow figwheel’s auto-compile-on-save is good enough for me, I’ll write some code, save, then do something in the browser to execute it. When I do want to connect emacs to a running REPL I’ll run cider-connect, however that means I need to have an nREPL server running. I know I have that in some projects, however I don’t think it comes for free with the lein template you just ran.

danielglauser17:06:32

For the way I suggested you need piggieback. If you follow the installation instructions here: https://github.com/nrepl/piggieback you’ll get an nREPL server that you can cider-connect to.

itruslove17:06:27

@chris_johnson I think it's that CIDER doesn't know about the cljsbuild source paths. Try adding src/cljs or whatnot to the top level :source-paths vector and restart the REPLs

8
gadfly36117:06:43

@chris_johnson I was able to reproduce the issue and @itruslove I think you're right about adding src/cljs. I'll go ahead and update the re-frame-template.

gadfly36117:06:10

Updated version deployed to clojars

🚀 8
Chris Bidler17:06:35

@itruslove @gadfly361 Aces, I added src/cljs and I’m (apparently) up and running. Thanks!

👍 8
lilactown19:06:03

what do you all think it would take to allow re-frame to be used with frameworks other than reagent?

mikethompson19:06:21

@tord by default, try to maximize the amount of state in app-db.

mikethompson19:06:59

@lilactown re-frame is about 600 lines of code, so a variation for another framework is not too hard. For example, there's citrus for Rum.

lilactown19:06:25

true. I’m pondering if it would be possible to polyfill reagent’s API

mikethompson19:06:27

(But I personally do not like many of the decisions taken in citrus ... eg use of multi-methods, rather than "reg-*").

lilactown19:06:26

e.g. I’m working on a hiccup compiler + some other libs that provide similar functionality to reagent without having a custom render queue or runtime interpretation of hiccup

lilactown19:06:42

in a sunshine, rainbows & unicorns world a re-frame app could swap out reagent with the bundle of libs I’m creating and it mostly JustWork(tm)

chris_19:06:01

Not identical; but similar.

lilactown19:06:15

yeah, what I’m interested in is providing a set of libs that are quite a bit simpler than reagent, architecturally

lilactown19:06:55

but I think that providing a higher-level library that combines them into something as powerful as reagent is necessary. and providing the ability to use with re-frame (which has become a de-facto standard almost for many new projects, a la Redux) would just be awesome

lilactown19:06:33

it looks like there’s a few places where re-frame hooks into the reagent render queue which might prove to be complicated to polyfill

danielcompton20:06:13

@lilactown I don’t think plain re-frame is hooking in to reagent, bar the tracing for re-frame-10x?

lilactown20:06:22

hrm. I see that router.cljc at least brings in reagent.after-render and is doing some tricky async event dispatching

lilactown20:06:42

I’ll have to spend some time digging into it more and actually understanding how re-frame works better

ScArcher20:06:34

Another beginner question, how do you typically "build" a re-frame application?

ScArcher20:06:47

Does it compile to an html file and a css / javascript file?

lilactown21:06:51

a re-frame app is a clojurescript app, so it will build the same way any other clojurescript will: to a javascript file

lilactown21:06:34

typically you’ll add that to an HTML file with a <script> tag, as well as add any CSS that your app needs to that same HTML file

ScArcher21:06:55

Yeah, it's doing that, but i'd like to build it minified if possible.

ScArcher21:06:17

under builds there's one that has an ID of "min"

ScArcher21:06:28

I just don't know how to tell lein to use that profile.

lilactown21:06:06

I’m guessing you’re using cljsbuild

ScArcher21:06:13

Yes, that's correct

lilactown21:06:52

you can run other builds via the command line lein cljsbuild once min

ScArcher21:06:54

@lilactown thanks, that's what I was looking for. I thought I had tried that already, but I guess not.

lilactown21:06:56

typically cljsbuild projects have a “dev” build which isn’t optimized and has some developer tools added into the mix, and then a “prod” build or what-have-you that runs advanced compilations

ScArcher21:06:59

Either that or i needed to do a clean first.

lilactown21:06:09

could’ve been it, ya. happy to help