Fork me on GitHub
#fulcro
<
2021-09-14
>
lance.paine13:09:15

Good afternoon. I think I must be missing something obvious. I'm getting this error: (Stale Output! Your loaded js is not produced by the running shadow-cljs instance... ) I had previously been running the fulcro-billing-app, but switched to a project cloned from fulcro-template, and was still being served the fulcro-billing-app. I ended up getting around it this morning by switching port to 3001, and thought no more of it. But machine just restarted, and now I'm getting the same again. I tried clearing browser cache, but I even get the same message in a different browser.

lance.paine13:09:45

Is there some build cache I need to clear somewhere, or something?

lance.paine13:09:38

looking in the shadowcljs ui, it says it's watching main

sheluchin13:09:13

Not sure, but you might be mixing up backend servers in both projects. Could be that you started a backend server in the billing-app and never stopped it, so your backend port is still serving from there. I'd look for a process using the port in question and kill it if it's there, then try again with the fulcro-template project. Take a look through this section and some of the surrounding ones https://book.fulcrologic.com/#_setting_up_a_server

👍 2
lance.paine13:09:32

i thought that at first. but have been through a pgrep java | xargs kill`

lance.paine13:09:41

this am. and a restart this afternoon.

lance.paine13:09:10

(and a kill of clojures, too)

lance.paine13:09:59

:man-shrugging:

thheller14:09:30

did you change some paths but still have the old output files lying arround?

thheller14:09:54

the error is telling you that the .js files you have loaded where not produced by the shadow-cljs instance currently running

thheller14:09:21

this can happen if you have changed some paths but didn't update your html accordingly, thus loading old files

thheller14:09:52

or sometimes when running 2 shadow-cljs instances in the same project concurrently

thheller14:09:22

figure out which files you are actually loading and where they come from 😛

lance.paine14:09:56

when you say "changed paths", what do you mean? do you mean, changed content of file, or changed some value named path in say deps.edn or other config file? This morning It looked very much like serving the other project. Which would make me think I had a phantom shadowjs hanging about, if it wasn't for both this morning, and this afternoon I refreshed the shadowjs ui between app ui refreshes and saw that it wasn't running.

lance.paine14:09:53

but I fully conceded I don't know what I'm doing yet. or might have misremembered something. when it was just this once, this morning, I assumed silly transient thing I'd done. When it happened again, I was most confused 🙂 hence the question!

thheller14:09:08

paths as in your actual .js files produced by shadow-cljs and where you access them. so your <script src="...">

👍 2
thheller14:09:36

so if you change :output-dir in shadow-cljs.edn for example

thheller14:09:57

one good test is deleting the files you think you are loading and loading your page

thheller14:09:19

if its still loading then go look for the correct files 😛

thheller14:09:40

could also just have changed :modules (which changes the filenames) but still loading the old files

lance.paine14:09:27

thanks! I'll dig through those options if i make it happen again. Appreciate the help 🙂

hadils16:09:28

Hi! What does '* mean in a query?

tony.kay17:09:24

* is a wildcard. It means "get everything". Unfortunately * is also a valid clojure symbol (that would resolve to multiplication) so you have to quote it so that you get JUST the symbol as data in the query

genekim17:09:18

I had written many weeks ago this: > In a Fulcro RAD form, you can get access to `:ui/current-rows`, which is a vector of maps (“denormalized form?“) —  `[{:trello-card/id 123, :trello-card/name "abc"}].` > I can then map over this a stateful component — e.g., `map component-ui current-rows`. > OTOH, when I try to access `loaded-data` or `filtered-rows`, they’re a vector of idents (e.g., `[[:trello-card/id 123]]`. How do I turn this into a vector of maps like `current-rows`from within the RAD form? (No access to state, can’t use denormalize.) > Thank you! I’m ridiculously pleased with myself that I was able to get this working here, although I suspect it breaks a bunch of rules. 🙂 https://github.com/realgenekim/trello-fulcro/blob/trello/src/shared/com/example/ui/trello_list_report.cljc#L433 1. @tony.kay @holyjak Is there a better way to have done this? I had to manually get the state, and did manually did the normalization. 2. Is there a better way to retrieve the page size? Thank you!

tony.kay17:09:23

@U6VPZS1EK the link is busted (or perhaps private)

tony.kay17:09:23

I don't remember what RAD report auto-adds to its query internally. You can run get-query on the report in a REPL and see. Pretty sure the cache stuff isn't there because that would cause a lot more query load during rendering, and you typically are not going to render those things. You really should not be directly using filtered-rows or loaded-data in the UI...current-rows is the intended visible data. I guess you might want a count of those, but you can do that on the vector of idents. If you really need other things, and the number of things is relatively small, then while it isn't ideal (for performance) you can use db->tree on (app/current-state this) to convert your idents to their denormalized counterparts. What you should do instead is customize the state machine for that report, so you can do these "other" calculations or whatever at a well-defined user event instead of on every render. For example, if you're trying to get some overall stats (how many cards have label X), you should do that in the state machine just after the data load, and put the statistic in the cache so that rendering is super fast (and isn't recalculating it every time).

tony.kay17:09:57

Stats on the current-rows is quite tractable, so calculating those in the UI is usually fine and even preferred (no cache to invalidate)

genekim18:09:14

Oh…. I knew that you’d mention creating a state machine, and I knew I’d roll my eyes when you did. 😆 But holy cow, you’re right — in one particular Trello list, there’s 2000+ elements, and the current approach seems fast enough. But I think putting that (get-all-rows) action makes a lot more sense in the “sort” portion of the state machine. Thanks again!

tony.kay18:09:20

Sure, It really isn't hard...here's an example of where I customized one, just so you can see it can be pretty easy:

(defn load-statistics [env]
  (let [{:project/keys [id]} (report/current-control-parameters env)]
    (uism/load env :local/stats TheStats {:params {:project/id id}
                                          :target (conj (uism/actor->ident env :actor/report) :local/stats)})))

;; Use the normal report state machine, but change the handler that post-processes running the report,
;; and adds an additional load that gets the stats
(defstatemachine report-with-stats
  (let [base-machine report/report-machine
        handler-path [::uism/states :state/loading ::uism/events :event/loaded ::uism/handler]
        base-handler (get-in base-machine handler-path)]
    (assoc-in base-machine handler-path
      (fn [env]
        (-> env
          (base-handler)
          (load-statistics))))))
and then I just added :local/stats to the ro/query inclusion after setting the machine:
ro/machine             report-with-stats
   ro/query-inclusions    [{:local/stats (comp/get-query TheStats)}]
And now those stats load when the report finishes loading. Just scan through the state machine definition. UISM is order-independent within a handler, so it's pretty easy just to call the existing handler and then patch in your logic.

❤️ 6
🤯 2
genekim19:09:07

Thanks for posting such a helpful example of implementing this inside of the RAD state machine, @tony.kay — it took a little studying to see how you extended the default RAD state machine. Very cool.

tony.kay20:09:03

I should probably add helper functions for common modifications like this...since it's pretty boilerplate

🙏 2
genekim17:09:21

(defn get-all-rows
  "This is a hack to return the entire normalized contents of sorted-rows -- 
   this is necessary, because only the current page is normalized. 
   I had to write this because I wanted to display all the trello card titles, so I could
   quickly scan them, and click on it to go to that page. "
  [{:ui/keys                [current-rows cache
                             selected-row loaded-data parameters
                             sort-by]
    {:keys [filtered-rows sorted-rows]} :ui/cache
    :as props}]
  (let [app       (resolve 'com.example.client/app)
        state     (-> @app
                    :com.fulcrologic.fulcro.application/state-atom
                    deref)
        page-size 10
        all-rows  (->> sorted-rows
                       (map (fn [r]
                              (let [[_ id] r]
                                (get-in state [:trello-card/id id]))))
                       (map-indexed (fn [idx itm]
                                      (assoc itm :trello-card/page-num (inc (quot idx page-size))))))]
                         ;(get-in state [:trello-card/id "53b1e53c90a0dea1e8ce5ec7"])
    all-rows))
Oops! I’ll fix it, but in the meantime… @tony.kay

tony.kay17:09:56

You can always get the state map with (app/current-state this)...no need to resolve, etc.

🤯 2
tony.kay17:09:32

The only problem with using the state map is that Fulcro isn't aware of any data dependencies this creates (e.g. if you read data from some random place from state, then Fulcro doesn't know to rerender your component if that random data changes if it isn't in the query)...BUT, if the data doesn't change over time (or there is some other reason you'd get the render on changes anyway), then it is perfectly fine to look in the db for "extra stuff".

sheluchin18:09:53

How should Fulcro/Pathom interaction be handled when the query might not return something? Example: I have a PizzaOrder that may have an associated list of Allergies. In most cases the join will return nothing, but sometimes there will be related allergies. My implementation currently results in something like this after load!:

{:order/id 1
 :order/allergies-list [:allergy-list/id nil]}
Which I suspect is a bug/error in my code. Idiomatic Clojure shouldn't have maps with nils, AFAIK, so my expectation is that if the order doesn't have an associated list of allergies, the :order/allergies-list key should not be added to the props map after a load!.

tony.kay18:09:38

The reason is in your implementation of the resolver. If there are no allergies, then either return an empty list or just elide the :order/allergies-list. The only way you get what you're getting is if you return something like {:order/allergies-list [{}]} and the normalization tries to create an ident out of that.

sheluchin19:09:44

@tony.kay the resolver looks like this:

(pc/defresolver order-allergies-list-resolver
  [{:keys [crux] :as env}
   {:order/keys [id]}]
  {::pc/input #{:order/id}
   ::pc/output [:order/allergies-list]}
  (log/debug "order-allergies-list-resolver" {:order/id id})
  (when-let [allergies-list-id (q/order-id->allergies-list-id crux {:order/id id})]
    {:order/allergies-list allergies-list-id})
  {})
Query:
[{[:order/id 2]
  [{:order/allergies-list
    [:allergies-list/id]}]}]
Result:
{[:order/id 2] {:order/allergies-list {}}}
And I've confirmed that is the only resolver being hit for this particular query.

tony.kay20:09:17

the empty map is the problem in the result

tony.kay20:09:55

when-let isn't sufficient...an empty map is still "true"

wilkerlucio20:09:32

the resolver code sent here will always return an empty map (need to use if-let and make the empty map the false path)

wilkerlucio20:09:12

wonder how you are getting the empty map under :order/allergies-list in this case

sheluchin21:09:26

Updating the resolver like this still produces the same result:

(pc/defresolver order-allergies-list-resolver
  [{:keys [crux] :as env}
   {:order/keys [id]}]
  {::pc/input #{:order/id}
   ::pc/output [:order/allergies-list]}
  (log/debug "order-allergies-list-resolver" {:order/id id})
  (if-let [allergies-list-id false #_(q/order-id->allergies-list-id crux {:order/id id})]
    {:order/allergies-list allergies-list-id}
    {}))
And setting the false-path to false likewise produces the same result.

sheluchin21:09:49

> when-let isn't sufficient...an empty map is still "true" > need to use if-let and make the empty map the false path Just to clarify these two points -- is returning an empty map okay in cases when the data cannot be found, or should it return false or nil?

sheluchin21:09:27

> If there are no allergies, then either return an empty list or just elide the :order/allergies-list. Eliding the key still produces the same behaviour as I described, but returning something like {:order/allergies-list '()} results in:

{:order/id 1
 :order/allergies-list []}
Which still feels wrong to me, as an empty list (vector) still means something different from no list (a props map without an :order/allergies-list key at all).

wilkerlucio23:09:42

empty map is fine, it shouldn't have the key on the output in this case

wilkerlucio23:09:09

I'm gonna try to reproduce your case here

wilkerlucio23:09:33

(ns com.wsscode.demos.repro-elide
  (:require [com.wsscode.pathom.core :as p]
            [com.wsscode.pathom.connect :as pc]))

(pc/defresolver order-allergies-list-resolver
  [{:keys [crux] :as env}
   {:order/keys [id]}]
  {::pc/input #{:order/id}
   ::pc/output [:order/allergies-list]}
  (if-let [allergies-list-id false]
    {:order/allergies-list allergies-list-id}
    {}))

(def registry
  [order-allergies-list-resolver])

(def parser
  (p/parser
    {::p/env     {::p/reader               [p/map-reader
                                            pc/reader2
                                            pc/open-ident-reader
                                            p/env-placeholder-reader]
                  ::p/placeholder-prefixes #{">"}}
     ::p/mutate  pc/mutate
     ::p/plugins [(pc/connect-plugin {::pc/register registry})
                  p/elide-special-outputs-plugin
                  p/error-handler-plugin
                  p/trace-plugin]}))

(comment
  (parser {}
    [{[:order/id 2]
      [{:order/allergies-list
        [:allergies-list/id]}]}]))

wilkerlucio23:09:45

outputs:

{[:order/id 2] {}}

wilkerlucio23:09:56

maybe there is some plugin that's messing with the output?

sheluchin23:09:37

@U066U8JQJ my parser is setup like this:

(ns sheluchin.parser
  (:require
    [sheluchin.resolvers]
    [sheluchin.mutations]
    [sheluchin.server.config :refer [config]]
    [sheluchin.server.db.mounts :refer [crux-node]]
    [com.wsscode.pathom.core :as p]
    [com.wsscode.pathom.connect :as pc]
    [taoensso.timbre :as log]))

;; helper from glam repo
(defn mk-augment-env-request
  [get-config-map]
  (fn augment-env-request
    [env]
    (merge env (get-config-map env))))

(def resolvers [sheluchin.resolvers/resolvers sheluchin.mutations/mutations])

(def env-additions
  (fn [env]
    {:crux crux-node
     :config config}))

(def pathom-parser
  (p/parser {::p/env        {::p/reader                 [p/map-reader
                                                         pc/reader2
                                                         pc/ident-reader
                                                         pc/index-reader]
                             ::pc/mutation-join-globals [:tempids]}
             ::p/mutate   pc/mutate
             ::p/plugins  [(pc/connect-plugin {::pc/register resolvers})
                           p/error-handler-plugin
                           (p/env-wrap-plugin (mk-augment-env-request env-additions))]}))

(defn api-parser [query]
  (log/info "Process" query)
  (pathom-parser {} query))

wilkerlucio23:09:21

is there any other resolver that might also output :order/allergies-list?

wilkerlucio23:09:22

with your setup I would expect the it to output ::p/not-found, as you are not using the plugin to elide special values, that output you saw from the REPL or calling from Fulcro?

sheluchin23:09:54

I have a log/debug in every resolver and I can see that no other resolver is being hit. I notice that the output is different in Inspect's EQL tab and in my REPL. Query:

(api-parser
   [{[:note/id 1]
     [{:note/x
       [:x/id]}]}])
EQL tab:
{[:note/id 1] {:note/x {}}}
REPL:
{[:note/id 1] #:note{:x :com.wsscode.pathom.core/not-found}}

sheluchin23:09:34

And sure enough, after adding p/elide-special-outputs-plugin, the REPL output for the query is as yours was: {[:note/id 1] {}}

wilkerlucio23:09:01

yeah, the problem is that Fulcro considered there was a key there, and then turns that into an empty map (because of the sub-query)

wilkerlucio23:09:59

should work fine with the elide plugins, please let us know if you have further issues

sheluchin23:09:36

Interesting. I just confirmed that with the elide plugin activated, the original issue that I noticed:

{:order/id 1
 :order/allergies-list [:allergy-list/id nil]}
now produces the correct result:
{:order/id 1}
The only change to my code was activating the plugin and changing from when-let to if-let.

sheluchin23:09:02

I suppose the importance of the elide plugin should be highlighted somewhere in the docs. I could make a PR for that if you'd like. Thank you both very much for helping me figure this out.

wilkerlucio00:09:28

yes, this caused a lot of pain already, that's why I removed the concept of ::p/not-found in Pathom 3

wilkerlucio00:09:39

so you just get a missing key in this same case

sheluchin15:09:31

It has (p/post-process-parser-plugin p/elide-not-found) just a few lines below. Looks like elide-not-found is a slightly more conservative option than elide-special-outputs since it doesn't elide reader errors. Do you still think e-s-o would still be helpful to add there? I was thinking of mentioning it in the Fulcro Book around where the first Pathom parser is configured, because without using either of those elide plugins, Fulcro behaviour can be unexpected/buggy in not-found situations. Maybe here? https://book.fulcrologic.com/#_the_communication

wilkerlucio18:09:32

true, I missed that, this would work the same in your case