Fork me on GitHub
#re-frame
<
2017-01-11
>
sandbags00:01:00

Am I misunderstanding the cljs-ajax API? When I use (ajax/json-response-format {:keywords? true}) I am expecting the returned body to be parsed as JSON and returned as a Clojurescript map. What I'm seeing is a JS object that appears to be some kind of string (`#object[String "function String() { [native code] }"]`). Am I just completely misunderstanding what's supposed to happen?

gadfly36100:01:13

@sandbags I have a wrapper for my calls that look like this:

(defn GET
  [path opts]
  (let [opts (assoc opts
                    :format (ajax/json-request-format)
                    :response-format (ajax/json-response-format {:keywords? true}))]
    (ajax/GET path opts)))
And i get the desired effect

sandbags00:01:40

I'm using the re-frame-http-fx extension with :response-format (ajax/json-response-format {:keywords? true})

gadfly36100:01:14

ahh sorry, forgot which channel i was in

sandbags00:01:20

@gadfly361 but, presumably, the issue here is the same ... by doing that do you get the (:body response) automatically decoded into a CLJS map?

gadfly36100:01:55

yeah, the response itself should be decoded into a cljs map, dont think you need to call :body

sandbags00:01:57

I'm not sure if I am just expecting something that isn't meant to be happening and I need to decode the string response into JSON myself

sandbags00:01:18

well in this case response also contains :status as well as :body

gadfly36100:01:38

"I'm not sure if I am just expecting something that isn't meant to be happening and I need to decode the string response into JSON myself" I think your expectations are fine

sandbags00:01:49

hrmm... i wonder what i am doing wrong then

sandbags00:01:09

the call works except that body is a JS object rather than a CLJS object

sandbags00:01:15

okay so it's just a regular JS string (I hadn't realised they all typed as function String() { [native code] })

gadfly36100:01:34

@sandbags, i gotta run, but hopefully some re-framers here can help you out 🙂

sandbags00:01:42

thanks n e way

dragoncube01:01:09

btw, is it ok to do re-frame related job postings here? 🙂

sandbags01:01:19

Well I'm baffled... I can't see why the json-read call isn't returning a CLJS object

kishanov01:01:56

What would be a good way to periodically dispatch the same event? I’m looking at timer example (https://github.com/Day8/re-frame/blob/master/examples/simple/src/simple/core.cljs#L18) and setInterval is just called, I want to conditionally setInterval and clearIntervals

kishanov01:01:35

Or, less abstract question: what is a good way to implement polling of REST API via GET requests? I’m looking at https://github.com/Day8/re-frame-http-fx/blob/master/src/day8/re_frame/http_fx.cljs and don’t get where is the good place to sneak js/setInterval

notanon01:01:35

setInterval was made for use cases like this (the name pretty much says it all).

notanon01:01:50

are you wanting to cancel the interval?

notanon01:01:31

not sure what more you're looking for other than polling an endpoint. set interval is definitely the most direct way to do that. any library you import to do it for you is just going to be doing a setInterval for you.

notanon02:01:30

i missed your comment between the links... a place to sneak the setInterval? does it need to be running all the time? set it in your init, or defonce it in your core ns.

notanon02:01:23

if you want to start it at only a specific time, (like when another event happens) then register an effects handler. https://github.com/Day8/re-frame/blob/master/docs/Effects.md

notanon02:01:15

if you want to cancel it later (in response to another event) store the int returned by setInterval in your app-db so you can call clearInterval on it

kishanov03:01:20

I understand how manually to do (assoc db :interval-id (js/setInterval #(re-frame/dispatch [:polling-event]))) inside reg-event-db, what I’m looking for is something that will do {:dispatch-polling [:event]} in reg-event-fx

notanon03:01:42

yeah thats the link i posted. register your own effect. e.g. replace the keyword :butterfly in the link with something like :start-interval and make another one for :stop-interval and in the effect handlers start and stop the interval.

notanon03:01:02

the built-in effects in re-frame are :db, meant for updating app-db. :dispatch, meant for triggering events

notanon03:01:30

you need your own effect which means what you want it to mean. in this case start/stopping intervals

notanon03:01:07

(there are other built-in re-frame effects, those are just the first two that come to mind)

notanon03:01:56

see what i mean?

kishanov03:01:14

would it be possible to build an interceptor that “wraps” any given event into setInterval?

notanon03:01:07

you can make interceptors that do anything, but they're only meant to modify the coeffects (inputs) and effects. i'm not immediately understanding the connection between them and what you're wanting. https://github.com/Day8/re-frame/blob/master/docs/Interceptors.md

mikethompson03:01:27

@dragoncube I, for one, have no problem with the odd jobs posting in here

mikethompson03:01:51

Having said that, there is a #jobs channel for that, I think

mikethompson03:01:16

@kishanov if you want to turn on and off periodic events, then perhaps something like this:

(reg-event-fx  
   :something
   (fn [cofx event]

      ;; turn on an interval 
      {:interval {:action :turn-on         ;; turn on or off
                  :id 123                    ;; what is the id of this interval
                  :frequency  1000               ;; every how many milli secs
                  :event   [:do-poll  42]}}))    ;; what event to dispatch 

mikethompson03:01:20

And also:

(reg-event-fx  
   :something-else
   (fn [cofx event]

      ;; turn off an interval we previously created (using the id we previously supplied)
      {:interval   {:action :turn-off   
                    :id  123}}}))

mikethompson03:01:46

So that's how you'd turn on and off intervals ... then all you have to do is write the effect handler for :interval

mikethompson04:01:55

(reg-fx 
  :interval  
  (let  [live-intervals    (atom {})]
    (fn [{:keys [action id frequency event]}] 
      (if (= action :turn-on)
         (swap! live-intervals 
            assoc id  (js/setInterval #(dispatch event)  frequency)))
         (do 
            (js/clearInterval  (get live-intervals id))
            (swap! live-intervals disassoc id))))

mikethompson04:01:06

untested and there's no error checking

len07:01:53

If we have a lot of data coming from an external system is there any way to manage the app-db, expire items etc ?

oliy11:01:58

Good news everyone! My re-learn library is now ready for use: https://github.com/oliyh/re-learn There is a write up of the rationale at https://juxt.pro/blog/posts/re-learn.html re-learn is built using re-frame and reagent and will integrate nicely with your applications to educate your users about all your cool features and updates

oliy11:01:18

End shameless plug :)

sandbags12:01:23

@danielcompton is there an obvious smoking gun as to why my :http-xhrio request might be returning a JS string rather than a CLJS map?

sandbags12:01:09

I added the :raw false as a kind of desperation move in case somehow the wrong code path was being taken

sandbags12:01:58

as far as I can see the response is coming back as JSON data with Content-Type: application/json;charset=UTF-8 but I'm getting a string back in the (:body response)

sandbags12:01:29

i'm puzzled

sandbags12:01:27

When I manually try (js->clj (goog-json/parse text) :keywordize-keys true) on the text being returned from my query I get what I expect

sandbags12:01:14

but even the raw output of goog-json/parse is not a JS string but a JS object

sandbags12:01:23

now i am very puzzled

sandbags12:01:53

i am sure i must be doing something quite simple wrong...

sandbags12:01:10

Anyone have any advice about patching in a replacement version of a library function?

sandbags12:01:30

if i wanted to patch in an instrumented version of ajax.core/json-read

sandbags12:01:42

i wonder if i can just redefine it via the REPL if I go to that namespace

sandbags12:01:20

Okay so what the everloving fuck is going on here....

sandbags12:01:17

it's almost like the entire json-read process never happens

sandbags13:01:48

Okay so by instrumenting json-read I can see that the final result looks right and is a cljs.core/PersistentArrayMap yet by the time it gets to my :on-success handler function either the result of the json-read has been ignored in favour of the raw response, or it has been mangled into a JS string

sandbags15:01:52

Okay well I have been banging my head against this problem (and the tooling to try and debug it) for a few hours now and I am no further forward than when I started.

sandbags15:01:00

If anyone can please help that would be appreciated.

kishanov15:01:39

@sandbags does your API endpoint return correct content-type? I had similar problem when it used to return not “application/json"

sandbags15:01:03

@kishanov i posted a little further up with the response Content-Type: application/json;charset=UTF-8

nrako16:01:42

@sandbags Here is a working snippet from a side project that I have. Not sure if it is helpful, but looks like your endpoint might be add an extra set of strings to the response body before returning?

sandbags16:01:34

@nrako thanks, your code looks pretty much the same as mine modulo my use of re-frame-http-fx

sandbags16:01:38

this is the response from my server

sandbags16:01:58

maybe i should check with some kind of json linter

joshjones16:01:19

sounds like an opportunity to take a break and bring a set of fresh eyes back to it after food and rest 😉

naomarik16:01:07

has anyone considered matching subscription keywords to clojure.spec keywords to easily map data validation?

nrako16:01:13

I'm on cljs-ajax 0.5.8 btw

sandbags16:01:14

linter says it's good

sandbags16:01:07

same version of cljs-ajax here

nrako16:01:21

and re-frame/http-fx 0.1.2

sandbags16:01:29

@joshjones i think you are probably right

joshjones16:01:15

I’m sure a fresh perspective on it after the passage of some time will immediately reveal a simply brilliant solution 🙂

sandbags16:01:30

well it didn't over night but we live in home

sandbags16:01:01

@nrako hrmm.. i seem to have re-frame/http-fx 0.1.3

sandbags16:01:51

i'm just going to downshift a second and see if that changes anything

sandbags16:01:49

seems to be the same with 0.1.2

sandbags16:01:57

what a waste of time

sandbags16:01:25

i have little doubt that, at the end of all this, i am going to discover some stupid schoolboy error that i have made

sandbags16:01:40

hello that's odd

sandbags16:01:39

oh maybe it's just the way the Chrome JS debugger is displaying it

nrako16:01:52

What about settings :keywords? to false

joshjones16:01:32

if that’s the reason, hey, it’s only picard-facepalm because we’ve all done that kind of thing, some of us multiple times

sandbags16:01:05

okay i will step away for a bit.

sandbags17:01:07

@oliy thank you, however I'm loathe to throw out everything i have done and embark on a new set of libraries and API's just because I am having a JSON encoding issue that is, most likely, some mistake i have made (since other people are, i guess, using re-frame-http-fx without this problem)

gadfly36117:01:19

@oliy re-learn and martion looks useful, thank for sharing 🙂

nrako17:01:52

@sandbags What's the server side code look like? I would be especially curious about middleware...

shaun-mahood18:01:18

@sandbags: This might be useless advice, but I was having some different issues getting JSON stuff to work - I switched everything to Transit to figure out where the problem was and the problems went away - my issues were JSON writing/reading specific and I was spinning my wheels thinking it was something else.

sandbags18:01:58

@shaun-mahood well, originally, i planned to just shovel EDN over the wire but, for some reason, i ended up using JSON

sandbags18:01:32

i think i've used transit before for serialising to localStorage so i might give that a go

sandbags18:01:43

but... i just can't help thinking there is some stupidly simple explanation here

sandbags18:01:54

http://jsonlint.com validates the JSON my server is responding with. The header Content-Type: application/json;charset=UTF-8 seems okay to me

sandbags18:01:43

my re-frame-http-fx invocation appears identical (in at least the obviously relevant areas) to the examples and what others have posted

nrako18:01:49

In :handle-created, perhaps you need to wrap the whole map in generate-string?

nrako18:01:59

instead of just the body

sandbags18:01:23

omg could it be that bloody stupidly simple

sandbags18:01:28

looking at the code i cribbed from i do that

sandbags18:01:02

oh, hrmm... wait a sec

sandbags18:01:17

no because i am returning a status, so i am expected to return a map

sandbags18:01:30

it's different to my old project because there i wasn't returning status info

sandbags18:01:39

that said, in this case i am returning 201

sandbags18:01:13

i think i was just being neat (i have a bit of an over-fondness for symmetry)

mf18:01:49

Hi, Hoping someone can give some insight to writing a test for a Form-1 structured component. I want my test to simply assert that the hiccup returned from my component is of the correct format e.g.

(defn simple-component []
  [:div "hello"])

;; test
(= (simple-component) [:div "hello"]) ;; => true
I'm invoking my component with () rather than [] in my test because it knows nothing of Reagent and doesn't interprete the returned value. This works fine for my simple component, however when it comes to testing a more complex component, problems occurr:
(defn simple-component []
  [:div "hello"])

(defn complex-component []
  [simple-component])

;; test
(= (complex-component) [:div "hello"]) ;; => false 
This test fails because complex-component uses [] to invoke simple-component and as such the hiccup returned from complex-component contains references to the simple-component function rather than interpreted values. I could update complex-component to invoke simple-component using () rather than [] however I believe this is bad practice because it will prevent simple-component having an independent React lifecycle. So my question (finally!) should I be invoking my component in my test with [] and attempting to interpret the returned value using Reagent? If so how do I do this?

sandbags18:01:08

@nrako OMFG thank you!

sandbags18:01:26

i think i want to weep... such a dumb fucking problem

nrako18:01:05

and it's only Wednesday. Glad it's sorted out...

sandbags18:01:30

i am truly grateful, i think i had convinced myself things were okay server side but someone (maybe it was even you) did mention something about a potential double-encoding

sandbags18:01:58

what I did should, I think, be okay as you can return status and a body but i think i shall question this another day and move on with my life! 🙂

sandbags18:01:36

oh well, i got to know ajax.core a bit better and figure out how to override library methods and play with the JS debugger which is not truly horrible

sandbags18:01:43

there are some pluses

sandbags18:01:36

@mf i guess you need to trigger a render of the hiccup somehow since otherwise its just a vector

sandbags18:01:50

have you looked at the tests for re-frame/reagent themselves?

sandbags18:01:58

maybe they are already doing this and you could crib their method

geoffs18:01:10

@mf I would not test the nested generated html for multiple components. I think testing the generated html of components gets a little... eh as it is

geoffs18:01:41

but adding testing the nested subcomponents to that will just really tightly couple your current implementation in an unnecessary way.

sandbags18:01:44

if [complex-component] is returning [simple-component] then you know it's right and if [simple-component] returns what you expect then you can assemble a chain of tests without trying to expand the entire markup

sandbags18:01:34

which as @geoffs is saying would be fragile

geoffs18:01:42

if necessary, I would just test that the returned hiccup will render a particular nested component:

(defn simple-component []
  [:div "hello"])

(defn complex-component []
  [simple-component])

;; test
(= (complex-component) [simple-component]) ;; => false 

sandbags18:01:50

okay now i have to remember why i wanted this goddamn token in the first place!

geoffs18:01:22

and obviously to be clear, I think the example components you've illustrated with aren't probably worth testing in the first place. I only test the hiccup for form-1 components that have conditional logic or looping etc.

oliy18:01:08

We don't unit test reagent code, we use devcards for that

oliy18:01:24

We unit test model code for subscription / event behaviour

oliy18:01:12

If someone wants to add a class or something to some markup do you really want your test to break? What value would that give you? What would the developer do to fix the test?

oliy18:01:09

Test data by comparing it to data. Test UI appearance with your eyes :)

mf18:01:36

@geoffs @oliy @sandbags Thanks for the responses

mf18:01:07

@geoffs abit more info, the contrived examples are not real code.

mf18:01:10

The complex-component I'm testing is infact more complicated, it uses a bunch of other private components, that I don't want consumers (my test-suite being one) to know about

oliy18:01:47

In either reagent or cljsjs/react there is a 'virtual Dom node' that you can mount a reagent fn into which will let you do what you want. I just wouldn't recommend it

mf18:01:31

@geoffs I want to take a behavioural approach to testing complex-component, so that the test is ignorant of the implementation details of complex-component (e.g. the private sub-components it uses)

mf18:01:20

As I see it the test is less coupled to the solution of complex-component if it knows nothing of it's internals

oliy18:01:53

@mf it sounds less and less like a unit test

mf18:01:03

fine call it an intergration test

geoffs18:01:03

yeah, agreed.

mf18:01:20

I don't get too hung up on the test categorization

mf18:01:45

I just want to test the outward facing behaviour of my component

geoffs18:01:47

and you may be getting less coupling to the implementation of complex-component at the expense of coupling directly to the implementation details of all the components that complex-component uses... this seems like a bad trade-off to me but YMMV

oliy18:01:04

@mf this is what you want:

(ns test-example.core-test
  (:require [cljs.test :refer-macros [deftest testing is use-fixtures]]
            [cljs-react-test.utils :as tu]
            [cljs-react-test.simulate :as sim]
            [dommy.core :as dommy :refer-macros [sel1]]
            [reagent.core :as reagent]
            [test-example.core :as core]))

(def ^:dynamic c)

(use-fixtures :each (fn [test-fn]
                      (binding [c (tu/new-container!)]
                        (test-fn)
                        (tu/unmount! c))))

(deftest increment-button
  (testing "on-click"
    (let [app-state (reagent/atom {:count 0})
          _ (reagent/render [core/increment-button app-state] c)
          node (sel1 c [:button])]
      (is (= 0 (:count @app-state)))
      (sim/click node nil)
      (is (= 1 (:count @app-state))))))

oliy18:01:37

c is the virtual dom node that you can render your reagent components into

mf18:01:16

@oliy that looks like what I'm after

mf18:01:52

@oliy @geoffs really appreciate the input and the discussion

oliy18:01:06

the way i personally prefer to test is like this : https://github.com/oliyh/re-learn/blob/master/test/re_learn/model_test.cljs this is testing the model with pure data, and the beauty is you could even run this test in the jvm with the latest re-frame stuff

mf18:01:19

@oliy I will take a look at this for sure.

mf18:01:52

I havn't used devcards

oliy18:01:26

it took me a while to understand what the point was, but now i get it there's no going back

mf18:01:59

@oliy what was the lightbulb moment for you?

oliy18:01:49

i think it was when i started working on an app which had to represent lots of state in the same component

mf18:01:01

right ok

oliy18:01:10

lots of different states, i mean

mf18:01:42

FWIW the real complex-component function I have doesn't manage state atm

oliy18:01:56

once you get above a few variables the permutations of state become too many to be able to manually shuffle your app into all those states to see what it would look like

oliy18:01:06

ok, but i use it on re-learn because re-learn can display lesson bubbles in many different places - top, bottom, left, right, top-left, bottom-right...

mf18:01:22

ok sounds interesting

oliy18:01:28

if i had a whole sequence of lessons in all those positions i'd have to click through to see them all

oliy18:01:33

with devcards i can have them all at once

oliy18:01:41

just getting a screenshot for you

mf18:01:48

ok cool

oliy18:01:59

so if i'm refactoring how they should appear (or adding a new one), it's immediately obvious when i've broken something and when it works

oliy18:01:17

i'm planning to write something on the juxt blog about this at some point in the future (comprehensive testing of re-frame apps) but in the meantime there's a good one on devcards already by @frankie https://juxt.pro/blog/posts/generative-ui-clojure-spec.html

mf18:01:37

@geoffs Interested in why you see the testing of the outward facing behaviour of complex-component adding tighter coupling to the implementation details of simple-component? As I see it this style of testing allows the implementation details of simple-component to be a black box because we don't know about what it does. We just know what we expect complex-component to return.

mf18:01:26

@oliy sounds useful, I will take a closer look.

mf18:01:55

@oliy really appreciate your info

oliy18:01:58

good luck @mf 🙂

geoffs19:01:19

@mf stepped away for lunch. I may not be understanding you super well, but there's an issue with testing "the outward facing behavior" of complex-component, when what's being tested is the hiccup content of it. The exact hiccup content is a detail of the subcomponents, and really also a detail of the complex-component that I wouldn't want to be coupled to. I'm not sure if that actually explains it any differently than I did before 🙂

dragoncube19:01:48

for the people who are interested, there is new re-frame specific job posting in #jobs

mf20:01:03

@geoffs thanks for the further clarification. My goal was to test complex-component as a pure-function, asserting that given a specific input the returned value (hiccup) was as expected. In my case complex-component is the only public function in my libraries namespace. complex-component uses private components (e.g. simple-component) in the namespace but these are not available outside the namespace. For this reason it makes sense to me to test the public component only. Anyway really appreciate your insights 🙂

geoffs20:01:55

:thumbsup: sure, np

joshjones22:01:12

I don’t know who wrote the docs portion of re-frame, but man it’s good. Kudos @mikethompson et al for making sense of a detailed framework