Fork me on GitHub
#fulcro
<
2017-09-01
>
wilkerlucio02:09:49

@roklenarcic remember that your query is a graph, you should always be able to make paths do reach your data needs

wilkerlucio03:09:13

@tony.kay do you have a suggestion on how to provide the app for the network layer now that it got removed from the start interface? my current solution is to initialize the atom before with a nil value and then reset! it right after, like this:

wilkerlucio03:09:25

(defonce app (atom nil))
(defonce start-app
  (reset! app (fulcro/new-fulcro-client :networking (some-network "url" app))) ; <- init before so this variable is available here

wilkerlucio03:09:43

but I think this a bit ugly, wonder if you have an alternative way to handle this

tony.kay04:09:33

@wilkerlucio I’m putting the networking in an atom, and passing the completed app from started-callback into it. Networking is a bugger…Om needs it to start, but some networking things need the complete app in order to update the UI, etc. Either way. Circular reference needs are never pretty, but it’s why I had to remove the parameter…the app you were getting wasn’t done initializing. There was no way to resolve it “inside”…well, a more significant API change would be necessary. It’s on the low-prio todo list.

tony.kay04:09:18

@euandre Thanks. Merged.

tony.kay04:09:04

@roklenarcic Load actions queue up reads. Reads are combined into one network call if possible (non-conflicting keys)

tony.kay04:09:36

as do load calls. AS long as you have the thread, any number of loads will attempt to coalesce into one larger read.

tony.kay04:09:48

The state swap is an optimistic (immediate) update. Loading takes whatever time loading takes. As @wilkerlucio said: if you need to work on the result of a load, use a post-mutation which is a parameter to load-action.

wilkerlucio04:09:06

@tony.kay thanks, that's ok, I was just making sure I wasn't missing some other way to do it, this one is currently working for me, I need the app in order to do those "remote jumps" I told you before

tony.kay04:09:02

Yeah. File uplaod networking needs it for progress updates, and websockets need it for server push. It’s why it got added in the first place, but the person that did that work didn’t realize they were creating a dependency loop where one or the other would break eventually.

tony.kay04:09:12

(which it did…which is why I fixed it)

tony.kay04:09:51

I probably should have made a better fix when I was touching it. It really just needs a two-phase initialization. I probably could have just changed the app parameter to an atom, and told you to keep the atom, so that later stages in init could have updated the atom to hold the final app.

tony.kay04:09:21

It was a half-baked fix…sorry 😕

wilkerlucio04:09:37

no problem, it's good that we find those cases sooner than later

roklenarcic08:09:16

what's the best way to load a list of items and have them appended (rather than replace) to list at the :target site?

cjmurphy09:09:32

@roklenarcic: IIRC there's an option :append, also :prepend. It should be described in the source code documentation.

cjmurphy09:09:08

oh I was thinking of integrate-ident!.

cjmurphy09:09:43

I think in your case you wouldn't use :target but go straight for a post mutation and use integrate-ident! there.

cjmurphy09:09:26

there -> meaning inside your mutation, that is your post-mutation.

cjmurphy09:09:20

And if integrate-ident! doesn't fit your needs you can always do it yourself.

roklenarcic09:09:03

sadly I don't get the entity in post-mutation, I'll need to reach into the state and pull it out, worse yet, I'll need to temporarily store the result somewhere to append it later

roklenarcic11:09:12

yeah I tried to do it myself, but after the load, the browser enters an infinite loop when trying to denormalize

roklenarcic11:09:26

I push the previous list of idents into the load, and then I return that same list with an extra item at first position, which is not an ident, and the browser dies

roklenarcic11:09:03

I can't solve a simple scenario where I need to load an entity and add it to a list

roklenarcic11:09:21

without browser entering into an infinite loop

cjmurphy11:09:56

@roklenarcic You are just dealing with data structures, keeping the state data structure normalized (with idents). So if it helped you could try out your mutation in Clojure. If you app state is small you could post it up somewhere and we could check if indeed your state is normalized.

roklenarcic12:09:21

I'll need to just turn the whole thing upside down and do it somehow differently

cjmurphy12:09:03

The :params should be simple bits of data that are in your UI. One param that :search/med-parent takes is :parent-list. You have it as something that is actually accessing your state. Position the call to load in a defui where that state already is.

cjmurphy12:09:30

After all all of your client state should be in a defui of some sort.

roklenarcic12:09:38

hm let me present a very simple contrived example

cjmurphy12:09:35

Even if you stick with the way you are doing it can you determine the value of (get-in @(om/app-state (:reconciler app)) [:medlist-by-level "vtm"]) before doing the load? Also you could try not grabbing that value but instead sending across a hard coded value.

roklenarcic12:09:37

imagine you have a list and a search box, every time a user hits enter, you run a load, which returns a list of data, denormalized of course. But instead of showing this list as is, you concat the new results to the ones you are already showing in the listbox (which contains idents)

roklenarcic12:09:04

how would you make that happen?

cjmurphy12:09:27

I would be accumulating the data in client state. The defui would refer to it - react would be making that happen automatically for me.

cjmurphy12:09:22

Data that has come from the server should never need to go back to the server.

roklenarcic12:09:40

how would you accumulate that? Currently the load replaces an element in state. My solution was to make load target the list itself, then pass the old list as parameter to load and have the load function return the concatenated list, which of course then contains a mix of idents (from old list) and new denormalized data. And it seems that it makes the browser hang.

cjmurphy12:09:00

mutation does accumulation.

roklenarcic12:09:02

I'm not sending the old list to the server

roklenarcic12:09:20

which mutation?

cjmurphy12:09:07

The only way to change client state is via mutations.

cjmurphy12:09:04

well a load changes client state for you, but it will be doing a mutation under the covers.

cjmurphy12:09:38

Where is your 'mix of idents (from old list) and new denormalized data.'?

cjmurphy12:09:03

If it is in app state then all of it should be normalized always.

cjmurphy12:09:47

If it is in a component (defui) then it should be de-normalized always.

cjmurphy12:09:29

load will put normalized data in your app state.

cjmurphy12:09:47

Your mutations will then keep this data normalized.

sundarj13:09:35

isn't the 'view' the search box fills up a piece of state? so you would have it in your app state, and then do a (swap! state update :ui/search-results #(into % results)), right?

roklenarcic13:09:57

I mean yeah, that's one solution

roklenarcic13:09:06

to load into a hidden prop

roklenarcic13:09:10

and then move it

roklenarcic13:09:29

btw load-field-action disallows params that are not the field key

roklenarcic13:09:16

like if I add :params {:sorted true} to load-field-action call it errors out with [ 8.302s] [om.next] Error: You attempted to add parameters for #{:sorted} to top-level key(s) of [{:parent ...}]

roklenarcic13:09:15

is there some kind of command I need to add to make load-field-action calls go through? Network implementation never gets called, I have 50 items in :fulcro/ready-to-load, and 0 items in :fulcro/loads-in-progress

claudiu13:09:50

@roklenarcic just built something like that. Search, debounce on 3 chars, with loading screen for enter and no loading + append for load more. I can share the code when I get home (at work now). 🙂

roklenarcic13:09:49

you loaded into a temporary key and then moved it in another mutation?

claudiu14:09:02

yep 🙂 don't think there's any way around that 🙂

claudiu14:09:21

post mutation after the data is loaded.

driphter14:09:46

I'm not sure if it's just me or not, but the first sentence from devcards/fulcro_devguide.E_UI_Queries_and_State is making very little sense to me. >Now that you understand the database format, how you get data out of that database via the queries, and how you build parser code once you're ready to get some UI on the screen via all of those things?

driphter14:09:03

It's leading to an "I'm Ron Burgandy?" moment lol

sundarj14:09:18

not just you 😛

cjmurphy14:09:22

This would be my attempt:

cjmurphy14:09:23

Now that you understand three things: 1./ the database format, 2./ how you get data out of that database via the queries, and 3./ how you build parser code You're now ready to get some UI on the screen. How do you do that (via those 3 things)?

driphter14:09:29

It's like reading a crazy Haskell one-liner after a long while of lisping; it seems like it should make sense to someone, but for me it's more akin to a simulated stroke.

sundarj14:09:16

Has anyone really been far even as decided to use even go want to do look more like?

tony.kay17:09:19

@roklenarcic your stack trace has nothing to do with you networking call. That stack trace is for pulling data for rendering. What you've shown for state initially looks fine as far as I can see, but it does seem like you've potentially done something to state that exposed a bug in Om's db->tree. Given how many people use that, I would be really surprised if your app State actually looked sane after your load. It seems like kind of a bad idea that you're both trying to simulate a whole different style of networking while learning the basics. I think you're causing yourself a lot more hassle than you need to. why don't you learn the basics, then worry about hooking up to an alternate form of networking?

tony.kay17:09:43

@driphter The docs get heavily edited over time. It's obvious that sometimes mistakes get made and confusing sentences result. I probably accidentally joined two sentences together and didn't read the second one to see if it still made sense.

currentoor19:09:47

@tony.kay what do you think is the best way to run my webserver with a single command (for testing)

currentoor19:09:34

I basically need to do the equivalent of env JVM_OPTS=-Ddev lein repl and go but in one command

currentoor19:09:43

ideally something that i can alias with and chain together with lein do start-webserver, compile-devcards, run-visual-regression-specs

currentoor19:09:14

that’s basically what i had with boot

currentoor19:09:07

So far I got this

:aliases {"start-webserver"           ["exec" "-ep" "(use 'arc.server-main) (-main)"]}
but I’m not sure if that’s an abuse?

currentoor19:09:12

Seems to work though 😅

tony.kay19:09:59

@currentoor why not make an uberjar and just run it?

currentoor19:09:40

is there a way to build uberjar and run with just one command? and can it be chained together with other “tasks”

tony.kay19:09:10

or lein run -m clojure.main -e “(require ‘fulcro-devguide.upload-server)” -e “(fulcro-devguide.upload-server/go)”

tony.kay19:09:24

(which is what I do for the upload server)

currentoor19:09:40

what’s the upload server?

tony.kay19:09:33

so yeah, what you’re doing seems fine

currentoor19:09:37

and is there a way to make it exit?

currentoor19:09:49

after i’ve done my visual testing?

tony.kay19:09:15

sure, just make an endpoint mutation that has System/exit as a body?

currentoor19:09:30

lol seriously?

currentoor19:09:26

i guess i could just have an exit alias pointing to "exit" ["exec" "-ep" "(System/exit)"]

currentoor19:09:47

and put that at the end of the list of commands to lein do

currentoor19:09:49

it would feel strange writing an endpoint in my webserver that causes it to kill itself 😅

currentoor19:09:08

i could probably put safe guards so it doesn’t happen in prod but still

tony.kay19:09:21

well, you have to have some communication for that. Either kill it from outside with a script, or put something inside

tony.kay19:09:41

You could easily make the namespace look for some JVM option that enables it, so it won’t be there in production

tony.kay19:09:16

(defmutation die ...
   (when (System/getProperty "enableExitMutation") (System/exit 0)))

tony.kay19:09:03

or realize that it is a multimethod and don’t even run the defmethod if the property isn’t defined:

(when (System/getProperty "...")
   (defmutation ...))

tony.kay19:09:29

an alias pointing to exit would run in a diff JVM, wouldn’t it?

tony.kay19:09:30

why do your visual regressions need a server?

tony.kay19:09:51

isn’t the data for them embedded in the js?

tony.kay19:09:03

If so, why not just run them from a file URL?

currentoor19:09:00

well our “html” looks like this

(defn cards [req]
  (layout
   [:title "Report Components"]
   [:body {:style {:background-color "#fff"}}
    [:script (str "window.build = \"cards\";")]
    [:script {:src "js/cards.js" :type "text/javascript"}]
    [:script {:type "text/javascript"} "window.DEVCARDS = true;"]]
   :tracking? false))

tony.kay19:09:15

ok, clojure it is 🙂

currentoor20:09:15

looks like you were right about (System/exit 0) not killing the webserver

tony.kay20:09:17

You could throw together a new namespace with a separate self-killing server and a main. Just have it time out after a minute or so

currentoor20:09:46

yeah that sounds simpler

tony.kay20:09:56

that way you leverage all the same server code, but when you run it from that entry point you don’t have to worry about shutting it down…it’ll just go away after perhaps a period of inactivity?

currentoor20:09:49

this was the sort of thing that boot excelled at

tony.kay20:09:50

throw in a bit of middleware at the front of the stack that resets a timer every time it sees traffic

tony.kay20:09:19

right, cause you were writing the build system within the same JVM…it wasn’t external executions

currentoor20:09:31

with boot i could compose tasks together in the same jvm

currentoor20:09:15

so unrelated question

currentoor20:09:23

any other languages peak your interest?

currentoor20:09:39

like if clojure didn’t exist, what do you think would be your pick?

tony.kay20:09:05

kind of off topic. We can pm 🙂

denis-v21:09:38

@tony.kay I've just had another go at my Person / Company example, using a separate ComponentName component, but I still have a couple of problems.

denis-v21:09:55

I'm not getting :company-name/by-id in the app state. And when I try to render the CompanyName component in Person render on line 75, I don't know how to get the correct name for the person