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
that one also doesn't remove link queries though
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.
I implemented the basic form thing and it works fine https://github.com/thheller/shadow-experiments/blob/master/src/dev/dummy/fulcro.cljs
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
(bind {:ui/keys [saving?]
:user/keys [id name settings] :as u}
(use-root :current-user User {}))
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
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.
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.
Same for application
. 99% React-free, but it has a tiny bit mixed in.
yeah I can imagine. getting the separation right is difficult
It isnāt too terrible actually. I think Iāve got most of it done (sans testing).
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.
alpha.raw pulls in both of those, and has use-root!
ohā¦missed something..sb
ad2425b9973af287f6b64fe9ad56808dd8c69eaa is the better SHA
the nc
moved to raw.components, and formc
to form-state
I have not tested this against live UI much, but all tests pass
neat! I'll check it out!
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.
Not sure what is optimal but you can always use the state (ie client DB)...
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)))))
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.
has anyone tried using guardrails with malli?
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?@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
No I haven't :( Please let me know if you figure it out!
Perhaps subforms require that the thing being edited is an entity of its own and not a part of a bigger entity?
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
^ Pathom placeholders solved this for me! But Iāll leave the comment above in case its useful to others
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
FYI https://clojurians-log.clojureverse.org/fulcro/2021-02-11
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?
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+))]))}))
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.
@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 š
Thanks, will do :)
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.
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
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.
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.
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.
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
I like this, I think Iām going to give crux a go regardless now seems really awesome
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.
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.
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.
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.
Demo repo for RAD is here: https://github.com/fulcrologic/fulcro-rad-demo
Thereās info for using it with each database in the read me.