fulcro

Book: http://book.fulcrologic.com, Community Resources: https://fulcro-community.github.io/, RAD book at http://book.fulcrologic.com/RAD.html
2021-04-05T01:01:11.341100Z

It's the global-eql-transform property of a fulcro app. I ran into this as well, was seeing mutations sending form config and other superfluous things to the server, I eventually implemented my own elision logic, but then later learned the fulcro rad stuff did the same thing: https://github.com/fulcrologic/fulcro-rad/blob/528bc320888c71e410c623e1ce2efb2e08b821a7/src/main/com/fulcrologic/rad/application.cljc#L92 It should really be the default for fulcro apps in general

2šŸ¤˜
2021-04-05T01:02:13.341500Z

that one also doesn't remove link queries though

tony.kay 2021-04-05T01:18:28.347Z

For those following along on the development of ā€œrawā€ Fulcro, Iā€™ve made a new commit at SHA cb6c90fedb14238f3718a4669c5583013c7605df with some cleanup and refactoring. There is now com.fulcrologic.fulcro.alpha.raw which contains functions for adding a root or UISM to Fulcro with no explicit use of React. The raw-components3 namespace was rewritten to leverage raw via React hooks, and a demo in react is in https://github.com/fulcrologic/fulcro/blob/feature/fulcro-3.5/src/workspaces/com/fulcrologic/fulcro/cards/composition4_cards.cljs#L330 I refined that UISM card some so that it works better (it assume you already had a session, so you can run through all of the real states). The main improvement here is that those of you wanting to play with Fulcro outside of the confines of React can now more easily do so. The library will still look for React in a CLJS context, of course, but using the raw ns allows you to just add functions that can receive prop trees as callbacks that run when Fulcro transaction steps complete (optimistic tx, return from remote, etc.) I think perhaps @smith.adriane and @thheller were looking to make use of this particular approach, and Iā€™m interested to see what else the community might come up with. Iā€™m not releasing a formal version yet, so use the git sha w/deps if you want to play with it.

1šŸ’Æ8šŸŽ‰
thheller 2021-04-05T11:23:42.350Z

I implemented the basic form thing and it works fine https://github.com/thheller/shadow-experiments/blob/master/src/dev/dummy/fulcro.cljs

thheller 2021-04-05T11:24:16.350200Z

there are however still a whole lot of react references everywhere so running it without react doesn't work. it does no rendering with react but form-state breaks if react isn't loaded

thheller 2021-04-05T11:26:09.350400Z

(bind {:ui/keys [saving?]
         :user/keys [id name settings] :as u}
    (use-root :current-user User {}))

thheller 2021-04-05T11:27:05.350600Z

thats basically the emulated raw3/use-root. I don't know if use-fulcro is actually needed anywhere when none of the react-factory or DOM things are used

tony.kay 2021-04-05T14:57:21.351900Z

I think thatā€™s right @thheller. I have not had time to review the dynamic vars completely, but the with-fulcro thing may not actually be necessary in most/all cases. Itā€™s going to take a little more refactoring to get React completely out of code paths. Dynamic routing is broken because it expects there to be an app root, and it sounds like form-state as well, though both should be fixable Iā€™d think.

tony.kay 2021-04-05T15:11:45.352300Z

Looking through it, itā€™s kind of a big project to isolate React as a library without breaking existing programs. I donā€™t think itā€™s terribly difficult, just a lot of tedious code movement of non-react stuff to new nses, and then making aliases from the original spots to them so that nses like form-state and such can refer to the common non-react stuff instead of the ā€œmixā€ that is components currently.

tony.kay 2021-04-05T15:13:57.352500Z

Same for application. 99% React-free, but it has a tiny bit mixed in.

thheller 2021-04-05T15:14:15.352700Z

yeah I can imagine. getting the separation right is difficult

tony.kay 2021-04-05T18:00:51.353Z

It isnā€™t too terrible actually. I think Iā€™ve got most of it done (sans testing).

tony.kay 2021-04-05T22:24:47.357400Z

SHA c5a0cfe60c281c4a25f7de966298b67a349fccab has a first pass that should do it for most nses. There is now a raw.application and raw.components ns that are the base for the legacy ones. I also put in some fixes related to using this all from Clojure. So, to try it out and avoid react completely, use raw.components and raw.application nses.

tony.kay 2021-04-05T22:25:45.357600Z

alpha.raw pulls in both of those, and has use-root!

tony.kay 2021-04-05T22:26:04.357800Z

ohā€¦missed something..sb

tony.kay 2021-04-05T22:27:32.358Z

ad2425b9973af287f6b64fe9ad56808dd8c69eaa is the better SHA

tony.kay 2021-04-05T22:27:45.358200Z

the nc moved to raw.components, and formc to form-state

tony.kay 2021-04-05T22:28:14.358400Z

I have not tested this against live UI much, but all tests pass

phronmophobic 2021-04-05T05:14:09.347500Z

neat! I'll check it out!

zhuxun2 2021-04-05T05:56:03.349300Z

Is there a way to store something on the action section of a mutation, and retrieve it on the ok-action section? I wanted to implement state recovery when the server responds a failure.

Jakub HolĆ½ 2021-04-05T10:44:10.349800Z

Not sure what is optimal but you can always use the state (ie client DB)...

2021-04-05T13:24:39.351600Z

I have used the runtime atom for that - communicating to the error-action in this case the prior state of the app if a network call fails in order to rollback to the prior app state.

(defmutation delete-goal
  [{:keys [goal-id]}]
  (action [{:keys [state]}]
    (let [goal           (get-goal-tree goal-id @state)
          all-goals-flat (goal-tree->vec goal)]
      (swap! state
        (fn [s]
          (reduce (fn [acc goal] (ns/remove-entity acc (dm/goal-ident goal))) s all-goals-flat)))))

  (remote [{:keys [app state-before-action]}]
    (let [runtime-atom (get app ::app/runtime-atom)]
      (swap! runtime-atom (fn [s] (assoc s :prior-state state-before-action))))
    true)

  (error-action [{:keys [app state result]}]
    (let [runtime-atom (get app ::app/runtime-atom)
          {:keys [prior-state]} @runtime-atom
          server-error (fu/get-server-mutation-err result)]
      (let [new-state
            (-> prior-state
              (assoc-in (dm/goal-ident goal-id :ui/submit-state) :state/failed)
              (assoc-in (dm/goal-ident goal-id :ui/server-message) server-error))]
        (reset! state new-state)))))

tony.kay 2021-04-05T15:03:12.352100Z

Technically the most appropriate answer is what @holyjak said: I would consider that ā€œstateā€, and state atom is what Iā€™d typically use. See form-state, for example: the entire basic mechanism of that is to make a copy of something (pristine) and then let you update it and try to save it. Ultimately you can either choose to revert it (copy the pristine back over the original) or commit the changes. Another thing to consider is that the params to the mutation are available in every section of a mutation, and do not change. You can close over whatever you want/need there, which allows you to control ā€œstate captureā€ from the UI. The runtime-atom is also a completely legitimate solution, particularly if what you want is more time-travel of state. Thatā€™s a lot harder to make user-friendly, because userā€™s donā€™t expect things to go back in time at some future point of interaction. A slow network interaction could allow them to move on to a whole other are of the app, which you then ā€œsnap themā€ back from.

Jakub HolĆ½ 2021-04-05T10:44:10.349800Z

Not sure what is optimal but you can always use the state (ie client DB)...

thheller 2021-04-05T11:23:42.350Z

I implemented the basic form thing and it works fine https://github.com/thheller/shadow-experiments/blob/master/src/dev/dummy/fulcro.cljs

thheller 2021-04-05T11:24:16.350200Z

there are however still a whole lot of react references everywhere so running it without react doesn't work. it does no rendering with react but form-state breaks if react isn't loaded

thheller 2021-04-05T11:26:09.350400Z

(bind {:ui/keys [saving?]
         :user/keys [id name settings] :as u}
    (use-root :current-user User {}))

thheller 2021-04-05T11:27:05.350600Z

thats basically the emulated raw3/use-root. I don't know if use-fulcro is actually needed anywhere when none of the react-factory or DOM things are used

2021-04-05T13:24:39.351600Z

I have used the runtime atom for that - communicating to the error-action in this case the prior state of the app if a network call fails in order to rollback to the prior app state.

(defmutation delete-goal
  [{:keys [goal-id]}]
  (action [{:keys [state]}]
    (let [goal           (get-goal-tree goal-id @state)
          all-goals-flat (goal-tree->vec goal)]
      (swap! state
        (fn [s]
          (reduce (fn [acc goal] (ns/remove-entity acc (dm/goal-ident goal))) s all-goals-flat)))))

  (remote [{:keys [app state-before-action]}]
    (let [runtime-atom (get app ::app/runtime-atom)]
      (swap! runtime-atom (fn [s] (assoc s :prior-state state-before-action))))
    true)

  (error-action [{:keys [app state result]}]
    (let [runtime-atom (get app ::app/runtime-atom)
          {:keys [prior-state]} @runtime-atom
          server-error (fu/get-server-mutation-err result)]
      (let [new-state
            (-> prior-state
              (assoc-in (dm/goal-ident goal-id :ui/submit-state) :state/failed)
              (assoc-in (dm/goal-ident goal-id :ui/server-message) server-error))]
        (reset! state new-state)))))

tony.kay 2021-04-05T14:57:21.351900Z

I think thatā€™s right @thheller. I have not had time to review the dynamic vars completely, but the with-fulcro thing may not actually be necessary in most/all cases. Itā€™s going to take a little more refactoring to get React completely out of code paths. Dynamic routing is broken because it expects there to be an app root, and it sounds like form-state as well, though both should be fixable Iā€™d think.

tony.kay 2021-04-05T15:03:12.352100Z

Technically the most appropriate answer is what @holyjak said: I would consider that ā€œstateā€, and state atom is what Iā€™d typically use. See form-state, for example: the entire basic mechanism of that is to make a copy of something (pristine) and then let you update it and try to save it. Ultimately you can either choose to revert it (copy the pristine back over the original) or commit the changes. Another thing to consider is that the params to the mutation are available in every section of a mutation, and do not change. You can close over whatever you want/need there, which allows you to control ā€œstate captureā€ from the UI. The runtime-atom is also a completely legitimate solution, particularly if what you want is more time-travel of state. Thatā€™s a lot harder to make user-friendly, because userā€™s donā€™t expect things to go back in time at some future point of interaction. A slow network interaction could allow them to move on to a whole other are of the app, which you then ā€œsnap themā€ back from.

tony.kay 2021-04-05T15:11:45.352300Z

Looking through it, itā€™s kind of a big project to isolate React as a library without breaking existing programs. I donā€™t think itā€™s terribly difficult, just a lot of tedious code movement of non-react stuff to new nses, and then making aliases from the original spots to them so that nses like form-state and such can refer to the common non-react stuff instead of the ā€œmixā€ that is components currently.

tony.kay 2021-04-05T15:13:57.352500Z

Same for application. 99% React-free, but it has a tiny bit mixed in.

thheller 2021-04-05T15:14:15.352700Z

yeah I can imagine. getting the separation right is difficult

tony.kay 2021-04-05T18:00:51.353Z

It isnā€™t too terrible actually. I think Iā€™ve got most of it done (sans testing).

2021-04-05T18:57:36.353500Z

has anyone tried using guardrails with malli?

2021-04-05T19:31:28.353800Z

https://github.com/teknql/aave

Tyler Nisonoff 2021-04-05T20:25:14.354600Z

Suppose I have a screen containing Entities A and B that looks like this:

-----------------------
|       A              |
|     =======          |
|    |   B  |          |
|    |======|          |
|       A              |
|    |======|          |
|    |  B   |          |
|.   |======|          |
------------------------
B is naturally a child of A, but when Iā€™m creating Fulcro components of this nature, I find myself needing to create a ā€œmeta/unionā€ component for B that encompasses the union of the query for all of the sub-views of B i want displayed, and then passing that prop to various view-specific functions / components that display just a sub-view of B in particular. The query would look like:
{
 :query [:component/id  {:component/subComp (comp/get-query (ui-union-B))}]
 :ident [:component/id :A]
}
Is there a better way for A to query for the individual views of B rather than using the union-component approach, or is that the best strategy? EDIT: maybe https://blog.jakubholy.net/2020/fulcro-divergent-ui-data/#_a_data_entity_spread_across_multiple_sibling_components are what I want in this case?

Tyler Nisonoff 2021-04-06T19:14:33.383500Z

@holyjak have you used pathom placeholders with subforms? It seems that add-form-config isnā€™t finding subforms if my subform is split into placeholdersā€¦and struggling to figure out how to get around it

Jakub HolĆ½ 2021-04-06T19:26:54.383700Z

No I haven't :( Please let me know if you figure it out!

Jakub HolĆ½ 2021-04-06T19:27:53.383900Z

Perhaps subforms require that the thing being edited is an entity of its own and not a part of a bigger entity?

Tyler Nisonoff 2021-04-06T19:50:44.384200Z

i think iā€™ll have to make a duplicate edge from A to B that doesnā€™t use pathom placeholders, seems the form state code assumes that any join is going to be flat, it doesnā€™t know how to walk the placeholder map

Tyler Nisonoff 2021-04-05T20:46:04.355400Z

^ Pathom placeholders solved this for me! But Iā€™ll leave the comment above in case its useful to others

1ā¤ļø
cjsauer 2021-04-05T22:04:29.356900Z

clj-kondo linting support for fulcro is coming along nicely! Iā€™ve written a custom hook that can understand fulcroā€™s defmutation macro, as well as simple support for guardrailā€™s >defn. Both will give you some simple error checking at lint time, as seen in the screenshots. PR for kondoā€™s shared config repo is here: https://github.com/clj-kondo/config/pull/12 If youā€™re keen to try out the bleeding edge, my fork is here: https://github.com/cjsauer/config/tree/fulcro-config

4
tvaughan 2021-04-06T11:21:30.370900Z

FYI https://clojurians-log.clojureverse.org/fulcro/2021-02-11

cjsauer 2021-04-06T12:18:47.373500Z

Oh nice! I tried searching for prior art and didnā€™t find this. However the link to the gist is broken for me. It also appears that a PR was never created. @adam678 were you still working on this?

tvaughan 2021-04-06T12:24:57.373700Z

Here's the version I have:

$ cat .clj-kondo/hooks/fulcro.clj
(ns hooks.fulcro
  ""
  {:author "Adam Helins"}
  (:require
    [clj-kondo.hooks-api :as hook]))

(defn defmutation
  [{:keys [node]}]
  (let [[_call
         sym
         & arg+]    (:children node)
        docstring   (first arg+)
        [[param+
          & fn-like+]
         docstring-2] (if (hook/string-node? docstring)
                        [(rest arg+)
                         docstring]
                        [arg+
                         nil])]
    {:node (hook/list-node (concat [(hook/token-node 'defn)
                                    sym]
                                   (when docstring-2
                                     [docstring-2])
                                   [param+
                                    (hook/vector-node (map #(let [[_sym
                                                                   arg+
                                                                   & body] (:children %)]
                                                              (hook/list-node (list* (hook/token-node 'fn)
                                                                                     arg+
                                                                                     body)))
                                                           fn-like+))]))}))

cjsauer 2021-04-06T12:40:17.376800Z

Thanks. This is a simpler approach I think. I was under the incorrect assumption that mutation handlers could refer to each other, but thatā€™s not the case (the :dispatch key in env holds those lambdas). On the flip side, I could see kondo implementing ā€œunused lambdaā€ lints one day, which would throw false positives with the above.

Helins 2021-04-06T12:47:59.377Z

@cjsauer Hi, here is my original gist: https://gist.github.com/helins/52d03847157b0dc95c6987844a74dd68 The PR slept out of my mind (had to work fast at that time) and I see you have already proposed something. Let me know it goes through, if not we can use that gist šŸ™‚

cjsauer 2021-04-06T13:06:01.377700Z

Thanks, will do :)

tony.kay 2021-04-05T22:24:47.357400Z

SHA c5a0cfe60c281c4a25f7de966298b67a349fccab has a first pass that should do it for most nses. There is now a raw.application and raw.components ns that are the base for the legacy ones. I also put in some fixes related to using this all from Clojure. So, to try it out and avoid react completely, use raw.components and raw.application nses.

tony.kay 2021-04-05T22:25:45.357600Z

alpha.raw pulls in both of those, and has use-root!

tony.kay 2021-04-05T22:26:04.357800Z

ohā€¦missed something..sb

tony.kay 2021-04-05T22:27:32.358Z

ad2425b9973af287f6b64fe9ad56808dd8c69eaa is the better SHA

tony.kay 2021-04-05T22:27:45.358200Z

the nc moved to raw.components, and formc to form-state

tony.kay 2021-04-05T22:28:14.358400Z

I have not tested this against live UI much, but all tests pass

Michael Rispoli 2021-04-05T23:06:48.362900Z

I was just wondering if anyone had any recommendations for a backend for use with fulcro preferably as a mono repo of some flavor. Just evaluating it for a project and was wondering how the backend data store pairs with the Frontend data store snd what kind of lift that ends up being.

2021-04-06T11:33:42.371100Z

I can highly recommend Crux (https://opencrux.com) for use with fulcro - it would fit for similar use cases as Datomic, but is open source and the team is amazingly nice and responsive if you have any feedback or questions

thosmos 2021-04-06T17:04:02.382Z

Iā€™ve only used Fulcro with Datomic, so Iā€™m biased. Fulcroā€™s query syntax was inspired by Datomicā€™s pull syntax and was originally designed to work directly with it. Pathom made it much easier to integrate with other DBs, but the pairing is still optimal.

Michael Rispoli 2021-04-06T23:57:21.390700Z

Thank you both, Crux looks pretty awesome in general Iā€™ve never heard of it. Iā€™m also wondering does anyone have a good full stack example of fulcro with a backend or template they use. I read the docs but definitely still feel really daunted getting started and would love to poke around with a good starting off point. Iā€™m also new to clojure generally so if fulcro is maybe a tough starting point let me know as well. It just seems like the best option for building a production app though as well.

2021-04-07T00:31:06.391900Z

I have a full-stack app template I base any new projects off of: https://github.com/dvingo/dv.fulcro-template fulcro may be a tough choice to start with, but as long as your expectations are accurate in terms of learning rate (I have a heuristic of giving myself about 3 months when learning something significantly new) then you should be okay - it would take a similar amount of time no matter what you pick, as you'll have to do the research and integration of a myriad of libraries which become your problem if you don't go with fulcro - in my opinion.

2021-04-07T01:47:09.392300Z

The reasons I like crux over datomic: ā€¢ setup. start a new node: (crux/start-node {}) ā—¦ scales from one node to many seamlessly ā€¢ no schema ā€¢ open source and gratis ā€¢ amazing team ā€¢ has a query planner so you don't have to ever think about the order of your datalog clauses again ā€¢ documents over entity attr value triples

Michael Rispoli 2021-04-07T02:04:24.393700Z

I like this, I think Iā€™m going to give crux a go regardless now seems really awesome

Michael Rispoli 2021-04-07T02:08:30.398500Z

I think the learning curve sounds about right. My team and I are coming from react js/ts backgrounds so I was looking to fulcro because like you said weā€™d end up trying to build something similar out of libraries and such anyway. Learning curve of a few months seems accurate too, I think we may want to stick to a few internal projects with looser timelines to learn a bit more.

Michael Rispoli 2021-04-07T02:10:30.400500Z

We did take up elm fir a few projects and sorta learned as we went and it was fine but this seems like there is a bit higher of a learning curve.

cjsauer 2021-04-05T23:46:13.365900Z

Iā€™d say regardless of backend data storage choice, the connective tissue with the client is always the same in Fulcro: pathom resolvers. So itā€™s pretty backend agnostic in that regard. Going with something youā€™re familiar with is likely a good choice. The problem domain might lend itself to one database over another as well.

1āž•
cjsauer 2021-04-05T23:48:16.367300Z

Fulcro RAD has helpful database adapters for Datomic, and I think SQL (not sure about the state of that at this moment), and just days ago there was a post for a Crux adapter.

1āœ…
cjsauer 2021-04-05T23:49:13.367800Z

Demo repo for RAD is here: https://github.com/fulcrologic/fulcro-rad-demo

cjsauer 2021-04-05T23:49:41.368600Z

Thereā€™s info for using it with each database in the read me.