fulcro

Book: http://book.fulcrologic.com, Community Resources: https://fulcro-community.github.io/, RAD book at http://book.fulcrologic.com/RAD.html
zhuxun2 2021-03-18T02:12:20.019Z

How come my df/refresh! did not trigger a rerender of the component?

tony.kay 2021-03-18T02:14:04.020800Z

components re-render when their data changes. If you don’t see a refresh, it is likely that you don’t have things correctly connected. Your query+ident+state must compose.

zhuxun2 2021-03-18T02:19:14.023900Z

Weird -- in the "element" tab of Fulcro Inspect, I can clearly see the component's ident. And in the "DB" tab I can clearly see that ident's corresponding DB entry has changed. I don't get it why the component is not re-rendered. Basically, I have a :onClick #(df/refresh! this) on the triangle. Before the user clicks on the triangle, the folder component already has the :dir/id and :dir/name loaded in [:dir/id "folder_id"] DB entry. Clicking on the triangle,should trigger loading of the folder's children, in the form of the :dir/sub-dirs field.

zhuxun2 2021-03-18T02:24:05.024900Z

So technically a new field :dir/sub-dirs is added to the DB entry of [:dir/id "folder_id"].

zhuxun2 2021-03-18T02:25:05.026100Z

I tried manually adding :refresh or :focus to the df/refresh! but the result is the same -- somehow the folder component is never re-rendered (shouldComponentUpdate is never triggered)

zhuxun2 2021-03-18T02:31:38.028500Z

Ahh, could it be because of the recursive query? Since I write statically {:dir/sub-dirs 1}, fulcro wont check the :dir/sub-dirs field in the second level even when it is now loaded

zhuxun2 2021-03-18T02:32:02.029Z

But how do I fix that?

zhuxun2 2021-03-18T05:44:27.030500Z

Looks like I need to use Dynamic queries, and every instance of Dir needs to have its own qualifier?

zhuxun2 2021-03-18T05:45:08.031500Z

If I do that, am I abusing the qualifier feature or is it the intended usage?

zhuxun2 2021-03-18T05:49:02.034500Z

And to do that do I need to make factories dynamically? Like:

(defsc Dir [..]
  {..}
  ...
  (for [sub-dir sub-dirs]
    ((comp/factory Dir {:qualifier (comp/get-ident this)}) sub-dir))
  ...
)
That seems quite wild to me

zhuxun2 2021-03-18T05:50:30.035600Z

BTW I just found that (comp/computed-factory) doesn't preserve the meta attached by (comp/factory) and thus breaks dynamic queries. Perhaps a bug

zhuxun2 2021-03-18T15:05:24.043900Z

@tony.kay Correct me if I'm wrong but after some further investigation I think what I'm trying to implement is perhaps very difficult in current Fulcro. I realize that much of the power of Fulcro comes from its ability (and thus requirement) to be able to build the entire query tree at any time. That's why we talk about dynamic queries as something to be considered carefully. If we were doing plain AJAX, we wouldn't even draw our attention to whether a query is "dynamic" or "static", because a query is just a query in that case. But in Fulcro, a query is not just a query, it's also part of the entire query tree that needs to be rigorously maintained and indexed.

zhuxun2 2021-03-18T15:09:51.047500Z

And to implement a lazy tree, what we are really doing is constantly modifying the query tree -- dynamically. Basically every instance of Dir needs its own dynamic query. Since these dynamic queries need to be tracked in the DB, we need to give them each a unique qualifier.

zhuxun2 2021-03-18T15:23:15.051600Z

However, right now this is extremely hacky to achieve. Fulcro at the moment assumes that each query is associated with a component class, or at least a factory (via qualifier). In order to implement the lazy tree we need each component instance to have their own mutable query.

Björn Ebbinghaus 2021-03-18T16:11:17.052100Z

Maybe I don't quite get your problem ... On your Ui Component you can make an unbounded query for :dir/sub-dirs And then you can modify the query for your load. You can add a component just for loading.

(defsc MyDirLoadHelper [_ _]
  {:query [:dir/id {:dir/sub-dirs 1}]
   :ident :dir/id})

(declare ui-dir)

(defsc Dir [this {:dir/keys [id sub-dirs]}]
  {:query [:dir/id
           :dir/name
           {:dir/files [:file/id :file/name]}
           {:dir/sub-dirs '...}]
   :ident :dir/id
   :initial-state
   (fn [{:keys [id name]}]
     {:dir/id id
      :dir/name name
      :dir/files []
      :dir/sub-dirs []})}
  (dom/div
    (dom/button
      {:onClick #(df/load! this [:dir/id id] MyDirLoadHelper)}
      "Load sub-dirs")
    (map ui-dir sub-dirs)))

(def ui-dir (comp/factory Dir {:keyfn :dir/id}))
Or you could use the :update-query on load!
{:update-query
 (fn [query]
   (-> query
     (eql/focus-subquery [:dir/sub-dirs])
     (assoc-in [0 :dir/sub-dirs] 1))))}
(You can probably improve this)

👍 1
tony.kay 2021-03-18T16:38:35.052300Z

Right, so, in general you want to separate in your mind the loading of data from the UI use of that data. Fulcro’s UI for this is dead simple and trivial…I agree that @mroerni’s approach is probably better. Just use full recursion, and only fill out the nodes that need more data on demand (user folding open an item). The folding open can be component local state if you have a tree that for whatever reason shows the same stuff twice (and you want them to not fold open at the same time).

tony.kay 2021-03-18T16:40:09.052600Z

“fold open” being just the difference in the rendering..the subtree, of course, will exist in both places since it is a normalized db

zhuxun2 2021-03-18T16:55:24.052900Z

I like @mroerni’s approach. However, the downside is that I have a "refresh view" bottom somewhere near the root of the entire view, and right now it simply does (comp/refresh! this). If I write full recursion, then I probably need to have a mechanism to prune the loading query and make sure that clicking on the "refresh view" won't load the entire file system.

zhuxun2 2021-03-18T16:59:04.053100Z

@tony.kay Like you said, I need to separate query loading from query definition. This separation of course comes with the bitter inconvenience of not being able to simply write (comp/refresh! this) in all the places. 😄

Björn Ebbinghaus 2021-03-18T17:03:00.053700Z

What exactly does this refresh do? You could load every dir in your normalized db again:

(defn refresh-dir [appish {:dir/keys [id]}]
  (df/load! appish [:dir/id id] Dir {:without [:dir/sub-dirs]}))

(run! refresh-dir (get (app/current-state) :dir/id))

👍 1
Björn Ebbinghaus 2021-03-18T17:05:23.053900Z

You would have to ask @tony.kay about how to load bulk data best. Never done it before.

zhuxun2 2021-03-18T17:12:00.054100Z

@mroerni The refresh view button is supposed to reload the entire tree view, updating the files according to the changes in the file system. I think what you wrote should work with some minor performance improvements. I would perhaps call refresh-dir recursively in order to avoid trying to load dirs (and their descendents) that are no longer there. Also I would combine all df/load! into one batch query.

zhuxun2 2021-03-18T17:15:30.054300Z

@mroerni These are very helpful suggestions. I appreciate your help!

tony.kay 2021-03-18T20:29:13.054600Z

I have not had time to finish the batched networking. There’s work in process on this branch here: https://github.com/fulcrologic/fulcro/commit/35a47b26f366833de26b496a1902b5232dba1431 So, at the moment, you’d just have to scan the “open tree” and issue loads for each level you’re interested in.