Fork me on GitHub

hi. Is there a way to have recursive queries between multiple components? There is examples in docs using '... for the same component. If it is not possible for different components, I wonder what is the best practice when two or more components are dependent on each other.


You’d be better off giving an example of exactly what you want.


here is a snippet:

(defsc Entity
   {:entity/keys [id ...]}]
  {:query [:entity/id
           {:entities/mentioned-in (prim/get-query Publication)}
   :initial-state ...
   :ident (fn [] (entity-ident id))}

(defsc Publication
   {:publication/keys [id ...]}]
  {:query [:publication/id
           {:publication/entities (prim/get-query Entity)}]
   :initial-state ...
   :ident (fn [] (publication-ident id))}


basically, they are referring each other in :query, which does not seem to be allowed.


yeah, queries are declarative, so you get an infinite depth data structure if you do that


what would stop said recursion?


One way to work this is to declar the rendering in plain functions…then you can manually make instances of components for each depth that share code:

(defsc Publication1
  {:query [{:entities (get-query Entity2)}]})
(defsc Entity2
  {:query [{:mentioned-in (get-query Publication3)}]})
(defsc Publication3
  {:query [{:entities (get-query Entity4)}]})


to your “max depth”


but that kinda sucks


In reality, chances are you only need one view at a time, and you could treat the other concern as a separate component that is populated later via a user event (“Show Mentioned In”)


you don’t really want this graph populated to some huge depth all at once. Instead, you might have a list of publications…and at some user request you might want to show the entities. Once an entity is shown you might want to list the publications of that entity, but probably with some search params, etc.


So, IMO you’re trying to make a UI query that represents your imagined graph, NOT the real UI you currently want to render.


Make your UI query match what goes on screen in the specific case, and work forward from there through time…don’t start with some overblown query and try to make UI work around it.


Thanks for the tips. Indeed, the queries contain more than what the actual UI needs. E.g., Publication does not need :publication/entities once it is referred via :entities/mentioned-in in Entity.


Hello! After reading a few chapters of the awesome Fulcro Developers Guide, and seeing the Pathom library being referenced a lot, i'm left a bit confused about how access to the persistent storage mechanism looks like. Does Pathom provide some unified API for both RDBMS (like PostgreSQL) and non-RDBMS databases like Datomic, Neo4j, CouchDB?


hello 🙂 What pathom does is the integration of multiple sources, normalizing data traversing using attribute names, that's what hte Connect feature does


there is a library called #walkable that does some integration with SQL, but this library was written for pathom before connect, so those two things don't have a good integration at this point


if just the SQL can solve your things, walkable may be a good option, but connect is way more generic and will handle better future changes


@wilkerlucio Sounds really interesting! but I will have to dig in a bit more and play with it for a bit since it's completely unfamiliar to me.


@icyiceling184 Yes, it does provide a unified API. If you are familiar with GraphQL? (it is the easiest comparison)


@hmaurer I do have cursory understanding of GraphQL: basically the client communicates using Queries and Mutations that are defined based on some graph model, and the server must implement resolvers which translatesthose Queries and Mutations into what the storage mechanisms understand. So with a RDBMS the resolvers would translate Queries into SELECT / COUNT etc. whereas Mutations would be INSERT / DELETE etc. There's a lot of low-level details involved in how the translation is performed, I guess that's what Pathom was meant to help tackle?


@icyiceling184 Right, that’s correct re GraphQL. From a high-level, you can think of Pathom as achieving the same thing: it provides a glue between your client, who wants to perform queries (aka retrieve data) or perform mutations (aka modify the data). The details, however, are a little different. As @wilkerlucio mentioned in the thread Pathom works at the attribute level, whereas GraphQL works at the entity level.

👍 12

How does Fulcro correlate a client side mutation to a server side one? Is it just by the mutation name?


@njj I'm not an expert but from my observation when trying to learn Fulcro myself, it seems that the client side mutation has the same name as the server side mutation (at least when I created a new fulcro project from lein new fulcro template those two files are found side-by-side under the src/main/model path). Sorry if that doesn't solve your question, and I would be also happy to hear a more elaborate explanation from some of the more experienced users.


@icyiceling184 thanks, I was thinking something similar. Some sort of combo of naming convention and path of file


Am I the only one who finds the Fulcro 2.0 Basics video series to have a bit too steep of a learning curve? I would probably benefit from a Fulcro 2.0 Intro to Basics series. Might be a combination of: - Being new to macro-heavy Clojure code - Being overwhelmed by the amount of namespaces / APIs involved - Being new to the concepts of Om.Next: EQL, Mutations, Queries, Client/Server dbs etc. Having a hard time here I guess 😅


you’re not alone, Fulcro isn’t for the faint of heart


At least in my experience so far 🙂 Been working on an old untangled app, slowly moving it to Fulcro


>Being new to the concepts of Om.Next: EQL, Mutations, Queries, Client/Server dbs etc. This is what got me as well. Learning Clojure(script) in my free time only and not being my main day to day job language does not help either.


I'm using fulcro in production. The first app was web-only. The current one is RN+web Fulcro isnt perfect. there is a lot of things that "i dont like", but it's way better then any other "web framework"


@souenzzo The thing that I find most compelling about Fulcro at this point is how it integrates all these great open source projects in order to create a complete story for the fullstack development workflow. It seems like it takes a lot of times to learn how to use things properly, but it also exposes you to state-of-the-art tooling/practices which if followed properly, will automatically make the end product system more powerful and easier to manage. Also, using Fulcro for small apps seems like an overkill, but I might be wrong here.


@souenzzo which are the parts you “don’t like”, out of curiosity?


1- heavy macro 2- heavy protocols/records 3- non-qualified keys 3 and 2 are related


I think @U0CKQ19AQ mentioned to me that (2) is’s legacy and that he would like to rely less on protocols.


This is true. My plan for F3 is to remove almost all protocols, make components essentially place a map of options on the component (so things like ident will just be an :ident key in an options map on the component whose value is a fn). This will make it a heck of a lot more extensible.


Macro-wise: The macros do give us some nice error checking opportunities that I am probably going to continue with. There are just a lot of things you can catch at compile time that are quite useful to catch…esp in defsc

👍 4

as far as non-qualified keys…you mean namespaced keywords? Original Om Next and Fulcro were written before spec and various revelations about the usefulness of this…I’ve been trying my best (without breakage) to move towards them.

👍 4

What about moving Fulcro to Haskell @U0CKQ19AQ?


why the heck would you want to do that??? Even less of an audience???


😄 but compile time guarantees!


I looked briefly at maybe Kotlin or ReasonML…but ugh, the syntactic overhead.


and Kotlin doesn’t have any kind of immutable literals, so writing queries would be nasty


I want the flexibility of Clojure…not the constriction of a type system. A type system cannot check that my props destructuring in the defsc has things in it that I forgot to query for.


Macros are MUCH more powerful than type systems, since they’re actual compiler extensions.


@U0CKQ19AQ hmm yeah, that’s true actually


Is the Fulcro client-side db a good fit for data that is actual graph-shaped? Looking at the 3.4. Graph Database section and the 5.9. Recursive Queries section on the Fulcro Developers Guide, I get the conclusion that the way Fulcro treats the client db is as a graph with relationships such as spouse, children is as edges of the graph. However, two problems pop in my mind relating to 3.4.1. Idents: 1. In the children example, only the parent has reference to per's children, whereas the children do not have a reference to their parent. That's not a complete graph model, since with a graph model you might need to pull in a child's parent for some operation. 2. In the spouse example, each spouse has a reference to per's partner. This seems like a source of bugs since the relationship is basically duplicated across the two entities, prone to getting out of sync. Suppose a married couple has undergone an unfortunate divorce, then it's possible that only one spouse is mutated from :person/spouse [:person/by-id 1] to essentially have the :person/spouse attribute removed, whereas the other spouse still has :person/spouse [:person/by-id 2] due to a developer forgetting to update in both places? in a graph data structure such operation would be atomic and leave no room for error. Basically i'm trying to figure out if for my actual need for a graph model on the client side, the Fulcro stack is a right fit, or I'd want to look for a different stack that enables a graph db on the client.


@icyiceling184 Yes, the Fulcro client-side db is a perfect fit for data that has a natural graph representation. 1. You could add a reference to the parent in each child, which would let you pull data about a parent from a child. 2. Well, it’s worth noting that the client-side db you are describing is not meant to be your principal data storage; it is meant to be a local “cache” of the data fetched from your server. Your server might very well (and probably should, for the reasons you pointed out) store a marital relationship as one entity instead of as two properties that have to be kept in sync. If you were using a graph database like Neo4j, it might be a bidirectional edge.


Basically the reason (2) requires two attributes to be kept in sync is because Fulcro has no notion of a bidirectional edge


@hmaurer Oh wow that makes a lot of sense re: >the client-side db you are describing is not meant to be your principal data storage; it is meant to be a local “cache” of the data fetched from your server However I'm a bit confused since I also know that Fulcro offers some features for Optimistic Updates. that feature would allow me to enable rich interactions driven from the client side's db: mutations would first operate on the client side db without halting UI and push the changes to the server db at some later time (asynchronously). this would mean the app is essentially independent of the server db and only uses it for long-term storage.


is that kind of vision not what Optimistic Updates feature capable of? maybe I misinterpretted it.


@icyiceling184 Yes, I was just going to say that’s the only gotcha here 😄 So, say you loaded a married couple on your app and have a button which should remove their marital status. This button will trigger a mutation which, on the server, will remove the bidirectional “married” edge (or however you’re representing that, it doesn’t really matter). Now, on the client, you could wait until the server processes the mutation and responds with updated attributes for the now unmarried couple, and update the cache then. But that’s often not very snappy, so instead what you can do is “optimistically” mark the couple as unmarried by removing their “spouse” attributes, and if the server fails for whatever reason (which hopefully shouldn’t happen too often), mark them back as married and display some error message. And yes, to do this optimistic update you will have to update the spouse attributes for both people to keep them in sync. You need to remember to do that, but the good news is that the client-side db is stored in a Clojure atom. I don’t know if you’re familiar with those, but they’re basically variables that support some operations designed for concurrency. Once of them is “swap”, which allows you to atomically update the value in an atom. This means that you can update both “spouse” attributes atomically; if something goes wrong while updating them the udpate won’t be applied: they won’t go out of sync (assuming your code isn’t wrong).

👍 4

@hmaurer that was a great writeup! you impeccably addressed my concerns. love it! you rock. You also taught me two things: 1. How the optimistic updates work, which gives me a mental model for proceeding with figuring out how to implement it for my usage. 2. The unbeknownst to me concurrent characteristic of the Clojure atom. Bravo! 😎