fulcro

Book: http://book.fulcrologic.com, Community Resources: https://fulcro-community.github.io/, RAD book at http://book.fulcrologic.com/RAD.html
Björn Ebbinghaus 2021-06-23T12:38:14.242400Z

@tony.kay Is there a semantic difference between nil props and {} ? Asking, because I noticed, that (computed nil {:something 42}) => nil and computed explicitly checks for nil props. (when-not (nil? props) ...)

Björn Ebbinghaus 2021-06-23T12:52:54.242500Z

Situation, where this caused a bug in my client, because there is no state for a child, as it only has a query from root:

(defsc Child [_ _ {:keys [something]}]
  {:query [[::uism/asm-id ::My-example-Router]]}
  something) ; => something is nil!

(def ui-child (comp/computed-factory Child))

(defsc Parent [_ {:keys [child]}]
  {:query [{:child (c/get-query Child)}]}
  (ui-child child {:something 42})
I thought I didn't need to include any initial-state for the child...

Jakub Holý 2021-06-23T15:11:41.242800Z

There is I think and if you have only a link query then you must have initial state, even if empty for it to work as expected https://book.fulcrologic.com/#_a_warning_about_ident_and_link_queries

Björn Ebbinghaus 2021-06-23T15:44:39.243Z

Thanks. I was sure, there is an explanation somewhere in the docs. 🙂

Björn Ebbinghaus 2021-06-23T15:47:48.243200Z

In this case, computed not working with nil is there so that it breaks intentionally? If, so. Maybe this would be a place to add one of these "Warning: You probably messed up." log messages.

tony.kay 2021-06-23T15:49:47.243400Z

if you got no props, then you should be able to detect that it is unnecessary to render a (stateful) component (because the props are nil)…computed should not change that, but it requires props to hang the computed data.

tony.kay 2021-06-23T15:50:06.243600Z

so yes, computed intentionally does nothing if there is “nothing” to work on

tony.kay 2021-06-23T15:50:21.243800Z

if it is a non-stateful component, then you don’t use computed, you can just pass whatever you want as props

tvaughan 2021-06-23T16:08:09.245600Z

Is there a way to apply nilify-not-found globally? Currently it seems like I have to add this to the pre-merge hook of every component, correct?

tvaughan 2021-06-24T21:07:05.263600Z

@tony.kay I created a gist on github, https://gist.github.com/carrete/fd98413cd3fb1a6b1ea649f5814e437f. This includes a portion of the actual component source, and two screenshots of the inspector showing two different server responses to the same query. The problem is triggered based on the server response. When the response to {:drawing/editor-session-ref [:editor-session/id :editor-session/pid]} is missing completely from the response map, things work as expected. But when the response is :drawing/editor-session-ref {:editor-session/id "..."} :editor-session/pid is rendered as ::merge/not-found, i.e. the function editor-session-link at the top of example.cljc in the gist returns "/svg-editor/::merge/not-found/". This is with fulcro v3.4.22 and pathom 2.3.1. This project is about a year and a half old. I'm not sure what you mean by "customized anything in the app." We didn't start with a template, though I took a lot of inspiration from the youtube videos (and my prior experience with om.next and reagent/re-frame). This has evolved quite a bit in the past year and a half, but other than including server-side rendering, I don't think we're doing anything out of the ordinary. Please let me know if there's anything else you'd like me to provide or do to help with this, perhaps create an issue on github if you think this could be a bug. Of course, I won't be surprised if you say I've done something wrong. This is also working for us now that we know how to work around this so please feel free to ignore this too. Thanks!

tony.kay 2021-06-24T21:38:20.264200Z

I can guarantee this isn’t right: https://gist.github.com/carrete/fd98413cd3fb1a6b1ea649f5814e437f#file-example-cljc-L89

tony.kay 2021-06-24T21:38:43.264400Z

you’re passing the props of one component to another, didn’t compose the query properly, and therefore the load cannot possible do the correct merge.

tony.kay 2021-06-24T21:39:23.264700Z

If the SVGEditor has a query, and is joined ot the component above it, it better darn well have a join in the parent query

tony.kay 2021-06-24T21:40:05.264900Z

This falls into the 90% category: most questions on this channel are incorrect query/ident/initial state. 😄

tvaughan 2021-06-24T21:43:43.265900Z

Thanks @tony.kay. I’ll go back to the documentation and try again.

1👍
Jakub Holý 2021-06-25T12:49:34.273600Z

@tvaughan perhaps https://github.com/holyjak/fulcro-troubleshooting can help to catch these problems earlier? Also https://blog.jakubholy.net/2020/troubleshooting-fulcro/ should be helpful in troubleshooting.

tvaughan 2021-06-25T13:03:56.274500Z

Thanks @holyjak

Tyler Nisonoff 2021-06-26T17:16:08.279100Z

I’m curious about the right composition approach here when the parent seems to be the same entity (and perhaps the same/similar query?) as the child component. I know pathom placeholders are one option, but that doesn’t work with form state.

tvaughan 2021-06-26T23:24:19.287600Z

Same. I thought it was recommended that components which are simply different views of the same data should share the same ident, and therefore props.

1👍
Tyler Nisonoff 2021-06-26T23:29:59.287800Z

I don’t think thats inherently true, as two views of the same data may expect different data (via different queries) For example, PersonView may only care about :person/name and :person/age But PersonDetailed may care about :person/name and :person/description We wouldnt want to pass PersonView props to PersonDetailed, because it’d be missing :person/description , even if they share the same ident However I’ve ran into a few situations where I’ve had multiple views on the screen all showing different fields of the same ident, and haven’t figured out a great way to modularize this in a way that plays nicely with form state + loads

tony.kay 2021-06-27T00:25:17.288Z

Two components that use the same data should use the same ident, so that they co-exist in the db, but all component should query for their own props. If I have a row in a PersonList called PersonRow, and a form called PersonForm those two very definitely would have the same ident. That does change the fact that they would have their independent and reified path in the database One lives in a table, the other lives on a screen for editing. If you have some weird case where you want a child, but that child is using the same props as the parent, then you have two options: 1. The child is a stateless component or just a plain function. It does not have a query or ident…if it purely a function used by the parent to render some content. It doesn’t participate in I/O, etc. It is a pure rendering concern. 2. Join the child in. That is fine as well, BUT the child must be properly joined, have its own query/ident/initial state SUCH that the parent joins the child in. Now, in this case since the parent and the child are the same, it means the state of that in the db will just be a loop:

{:thing/id {1 {:thing/id 1 
               :thing/parent-fact initial-parent-fact
               :thing/child-fact initial-child-fact
               :thing/child [:thing/id 1]}}}

tony.kay 2021-06-27T00:25:32.288200Z

and the components would be:

(defsc Child [this props]
  {:query [:thing/child-fact]
   :ident :thing/id
   :initial-state {:thing/id 1
                   :thing/child-fact initial-child-fact}}
  ...)

(defsc Parent [this props]
  {:query [:thing/parent-fact {:thing/child (prim/get-query Child)}]
   :ident :thing/id
   :initial-state {:thing/parent-fact initial-parent-fact 
                   :thing/id 1
                   :thing/child {}}
  ...})

Tyler Nisonoff 2021-06-27T00:30:48.288700Z

Thanks Tony > That does change the fact that they would have their independent and reified path in the database One lives in a table, the other lives on a screen for editing. Similar to the example above, how would you handle loads in this case? I feel like I end up mixing up UI-concerns (edges in my UI database) with server concerns (pathom resolvers that can resolve those edges) So I can use my UI component composition to handle my loads. I can choose to use a separate component all-together for loads, but then now I have to remember to update this LoadComponents query any time I update any of my sub-components. Is that just a trade-off we have to make for ourselves?

tony.kay 2021-06-27T00:31:03.288900Z

If you are going to load this from a server, then you have to make a resolver that understands that :thing/child is just a loopback. This is what placeholders are for in Pathom (plugin that allows you to use them without having to write a special resolver)…so whatever naming convention you use for placeholders (often > as a prefix) will make pathom work with this nesting full stack

tony.kay 2021-06-27T00:31:18.289100Z

and in that case, of course, no need for initial state since it all comes from the server.

Tyler Nisonoff 2021-06-27T00:32:47.289300Z

got it Only downside of pathom resolvers is i believe form-state doesn’t understand it, but perhaps thats too much of an edge-case. (For example, I have a view where the left-side panel is a form of an entity, and the rest of the screen contains many different views / graphs of that entity)

tony.kay 2021-06-27T00:32:56.289500Z

and yes, if you want to split your UI up and have the localized queries then you have to add the pathom placeholders plugin to get it to work well. But notice you cannot even imagine doing that in stock GraphQL (well, you could add it to the schema, and the add resolvers, etc…but being able to arbitrarily do this kind of magic from the client with no backend coding is kind of magical IMO)

tony.kay 2021-06-27T00:33:34.289700Z

Ah, form state. So, I did not design form-state with this in mind, no, but that isn’t to say that it shouldn’t be capable of dealing with it

tony.kay 2021-06-27T00:34:10.289900Z

That said, in my experience you can just break it apart with case (1) (stateless children/functions) and not nest the queries.

1👍
Tyler Nisonoff 2021-06-27T00:34:45.290200Z

to get around this, I added a true edge in pathom that I use for the form, and use the original edge for the other views (split up with pathom placeholders)

Tyler Nisonoff 2021-06-27T00:35:01.290400Z

But I’ve also experimented with the stateless approach like you’ve mentioned

Tyler Nisonoff 2021-06-27T00:35:16.290600Z

Thanks, this was helpful to talk

tony.kay 2021-06-27T00:35:42.290800Z

sure. I don’t really see how your case involves form-state? The rest of the screens has many different views?

Tyler Nisonoff 2021-06-27T00:37:16.291Z

Lets say the parent component is the whole screen The left-screen child is a form of entity A The right-screen child consists of various Views of entity A

Tyler Nisonoff 2021-06-27T00:37:27.291200Z

So my query on the parent looks something like:

Tyler Nisonoff 2021-06-27T00:38:39.291400Z

[{:parent/editable-A (comp/get-query FormA)}
                          {:parent/view-A [{:>/view-1 (comp/get-query ViewA1)}
                                           {:>/view-2 (comp/get-query ViewA2)}
                                           ]}]

Tyler Nisonoff 2021-06-27T00:39:14.291600Z

parent/editable-A and parent/view-A point to the same entity in pathom

Tyler Nisonoff 2021-06-27T00:40:05.291800Z

oh and I think form-state was involved here because the form started from the parent

Tyler Nisonoff 2021-06-27T00:40:25.292Z

otherwise it may have worked as a pathom placeholder?

tony.kay 2021-06-27T00:42:42.292300Z

Oh, I see, so your question was “how to handle loads”. There are several possibilities: 1. Issue loads for both. The merge is built to handle that correctly. This, in fact, is what the merge/not-found marker is about 😄 2. Issue loads for a side that is “complete” and then use targeting to patch the loaded data into the other side. 3. Generate a component (with no UI) that has a unified query for all data needed, load that, and then use targeting/post mutations to rework the graph as needed. 4. Generate pathom edges that mimic your UI shape so you can load from the parent. I find this least desirable just because it requires a lot of UI-related stuff coded into your back-end. Placeholders are nice because they don’t require addition code that is UI-related on the back-end, just a plugin. In terms of form-state that is more just initializing it correctly one the thing is loaded. That can be pre-merge, post-mutation, etc.

1👍
Tyler Nisonoff 2021-06-23T17:11:44.245700Z

In the Fulcro RAD source code: Theres an example of a pathom plugin that replaces ::p/not-found with nil — is that sufficient? com/fulcrologic/rad/pathom.clj -> (p/post-process-parser-plugin p/elide-not-found) ->

(defn elide-not-found
  "Convert all ::p/not-found values of maps to nil"
  [input]
  (elide-items #{::not-found} input))

tvaughan 2021-06-23T17:18:22.245900Z

Thanks @tylernisonoff. I'm not using RAD, but I am using Pathom with elide-not-found. Provided I understand things correctly, Fulcro then turns these nils into :com.fulcrologic.fulcro.algorithms.merge/not-found once received on the client-side. Note the use of nilify-not-found in the Countdown component at https://book.fulcrologic.com/#_pre_merge_with_server_data

Tyler Nisonoff 2021-06-23T17:20:35.246100Z

ahh got it, i’ll poke around — with my RAD set-up I never see not-found in the client db, so something must be fixing your issue (assuming thats what you’re referring to)

tvaughan 2021-06-23T17:25:23.246300Z

I can see in the inspector that things do indeed come back as nil. From that same link a bit further down: 1. During the normalization step of a load Fulcro will put `::merge/not-found` value on keys that were not delivered by the server, this is used to do a `sweep-merge` later, the `merge/nilify-not-found` will return the same input value (like `identity`) unless it’s a `::merge/not-found`, in which case it returns `nil`, this way the `or` works in both cases. On a re-read, now I'm not sure if what I'm seeing is expected behavior or not

Jakub Holý 2021-06-23T18:16:55.246500Z

What behavior are you seeing? IMHO there is never :not-found inserted into the DB. It is visible in pre merge but later gets removed / ignored, I believe.

tvaughan 2021-06-23T19:21:55.246800Z

> What behavior are you seeing? The value of a property in a component is not-found. This property has a value of nil set by the backend. Pathom correctly strips this property (key and value) from the response.

Jakub Holý 2021-06-23T19:29:05.247100Z

So the client DB physically contains the :<some ns>/not-found key as the value of the prop, i.e. (get-in client-db [:my-entity/id :some/prop]) returns this kwd?

tvaughan 2021-06-23T19:31:35.247300Z

(:some/prop props) returns not-found. I don't see not-found in the inspector

Jakub Holý 2021-06-23T19:36:40.247500Z

Hm, that is strange. What if you do fdn/db-&gt;tree ? Do you see the not-found there?

tvaughan 2021-06-23T20:13:10.247700Z

Sorry, I don't know what I'm doing wrong, but this keeps coming up {}

tvaughan 2021-06-23T20:24:40.247900Z

I got something to print, but it's just state machines, sessions, and site chrome. The components I'm dealing with don't show up

tony.kay 2021-06-23T22:49:52.248100Z

The ::not-found marker is only present during the guts of merge, so that merging works “as expected” (things you didn’t ask for cannot be affected, but things you did ask for that are missing get removed). :pre-merge has some specialness, since it is sort of participating in this story. So, not sure what you’re asking exactly.

tony.kay 2021-06-23T22:50:33.248300Z

The built-in behaviors of Fulcro will not propagate ::not-found to app state

tony.kay 2021-06-23T22:51:19.248500Z

though you can, of course, put it there. I don’t use pre-merge much, so off the top of my head I do not remember what extra things you need to do. I’d honestly have to read the book (or source) again to remember.

tvaughan 2021-06-23T22:57:28.248700Z

> So, not sure what you’re asking exactly. (defsc FooBar [this props] {:query [:some/name]} (dom/div (:some/name props))) renders as &lt;div&gt;:com.fulcrologic.fulcro.algorithms.merge/not-found&lt;/div&gt; even though this prop is returned as nil by the backend (more accurately :some/name nil is removed by pathom from the response map). I would like to know how to make sure that (:some/name props) returns nil not ::merge/not-found.

tvaughan 2021-06-23T22:59:44.248900Z

The only thing I've found relevant in the fulcro book is the use of nilify-not-found in the pre-merge hook. It's not that I want to use the pre-merge hook. I assume that this is either the correct way to solve this problem due to lack of any other documented approach that I've been able to find, or I'm doing something wrong.