Fulcro Troubleshooting Decision Tree (WIP)

  1. Key troubleshooting insight from Fulcro’s author
  2. The key to troubleshooting
    1. Terminology
    2. Know your tools!
  3. Troubleshooting steps by problem
    1. Frontend (Fulcro)
    2. Backend (Pathom)
    3. Fulcro RAD
  4. Common sources of error
  5. Demo of a troubleshooting process
  6. Related resources
  7. Still in need of help?

A decision tree to help you go from a problem to the most appropriate troubleshooting steps.

Work in progress (Edit on Github | Commit history)
This page is under active development and is updated irregularly, sometimes multiple times a day. Refresh often.

Key troubleshooting insight from Fulcro’s author

[..] a guiding principle that always helps me: Rendering in Fulcro is a pure function of state. Get the state right, and rendering will follow. The query/ident/composition has a shape that exactly matches the expected structure in app state. You do have to reason over that in time, but you need to learn to adapt to look at the data patterns.

Anytime I have a “bug” I first look at query/ident/initial state and see if they make sense, THEN I look at app state and see if it matches what makes sense, THEN I look at my logic and see if it is also working with that same data shape. It’s all about the data.

— Tony Kay

The key to troubleshooting

Fulcro is fundamentally simple and so is, in essence, troubleshooting it. It consist of a number of small, simple parts connected together. All problems stem either from not using a part correctly or not connecting them correctly together (the price of Fulcro being more library-ish then framework-ish). The key to troubleshooting is then to find the relevant part and zoom in on it and then to check all the relevant connections up to the UI. And you need to know how to optimally troubleshoot these various parts.

An early distinction to make is whether it is a frontend problem (mostly a matter of the correct query/ident/initial state/composition into parent) or a backend problem (mostly a matter of resolvers and their registration with Pathom or the underlying data sources).

For example. if the UI is not displaying the expected property, you might find out that it is because the server does not return it (connection). You zoom in to the resolver responsible for it (the simplest part) and the DB query it calls (even simpler sub-part) - is the query working? Is the resolver working? Is the resolver registered with Pathom? Is the query actually correct? The wrong solution is to stay at the level of the UI and work with the full stack while trying to locate the root cause of the problem.

Stuart Halloway’s Debugging with the Scientific Method offers a great approach to such a troubleshooting.

Terminology

A few necessary clarifications:

Attribute

or https://book.fulcrologic.com/RAD.html#attribute_centric[RAD attribute] is a map produced by RAD’s defattr, which contains a _Property keyword together with its value type and arbitrary metadata.

Property

a.k.a. "EQL property" is a keyword used in an EQL query and present as a key in the returned map. This is the term the EQL Specification uses while Pathom documentation mostly talks about "(data) attributes". I chose "property" over "attribute" to avoid confusion with RAD attributes. I only use "attribute" in this sense in connection with the Index Explorer to be aligned with its terminology.

Know your tools!

It is crucial that you know your tools so that you can really focus on the relevant part of the sytem and to explore / invalidate a particular hypothesis about it and the cause. Primarily this means that you need to have Fulcro Inspect working and be familiar with its many powers:

  • Use DB (screenshot) and DB Explorer screenshot to look at you client-side data

  • Use Transactions (screenshot) to see what is happening in the app (transact!, load! etc.)

  • Use Network (screenshot) to see requests sent to the backend (typically triggered by a load! transaction of a mutation) and use the [Send to query] button on a network request details to send the query to the EQL tab for further experimentation

  • Use EQL (screenshot) to try queries and mutations. Remember to [(Re)load Pathom Index] to get autocompletion. (And re-load it whenever you register / change a resolver in a significant way.)

  • Most importantly, use Index Explorer (screenshot of attribute list, attribute details, a resolver, the graph view of an attribute) to see the properties and resolvers known to Pathom. As with EQL, remember to [Load index] whenever you register / change a resolver.

See the Demo of a troubleshooting process further down for screenshots of the tools and a demonstration of how they are used in practice.

Troubleshooting steps by problem

Frontend (Fulcro)

Frontend errors are most often in incorrect query/ident/composition.

Expand All (Click to expand, double click to collapse.)

  1. Fulcro Inspect not working properly

    1. DB and DB Explorer does not work / shows no data

      1. Try:

        1. Restart Chrome Dev Tools

        2. Re-install the Chrome extension

        3. Use the standalone Inspect Electron app instead

        4. Check the error logs of the extension and of Fulcro itself

      2. Update to the latest versions of Fulcro and Inspect - some versions don’t work well together (as of 12/2020 you need Fulcro 3.4+ with the latest Inspect)

    2. EQL: No autocompletion → [(Re)load Pathom Index] (the button to the very right)

    3. Index Explorer (a.k.a. Pathom Viz, documented here) - if [Load index] does not work → make sure there is a resolver for the index query defined and registered with Pathom; see this example in RAD

  2. Data in the client DB missing / wrong How to recognize: the data is not showing up in the UI and when you look into the client DB using Fulcro Inspect (example screenshot), you do not find them there either. (If the data is in the client DB but not in the UI then look at the next point.) Now, based on the presumed origin of the wrong/missing date:

    1. If the source of the missing/wrong data is load! from the server:

      1. Missing data

        1. Has a load actually been issued? Did the load fail? Did the server return an error or an empty response? Check the Transactions (example) and Network (example) tabs in Fulcro Inspect, check the log on both the client and server sides.

        2. Is there a problem with the query / backend? (Example)

          1. Find the request in Fulcro Inspect’s Network tabs and click [Send to query] (screenshot).

            1. Does the query look ok? Does it include what you expect?

            2. Run EQL and check the result (example). If you believe the problem is with the backend, see below how to run and troubleshoot the query there

      2. The data is loaded but not normalized correctly

        1. Perhaps the corresponding entity is missing :ident (which is required for normalization) or it is not correct? (Remember to distinguish the lambda x template x keyword form of ident.)

      3. The data is there correctly but not connected to the graph at the correct place(s) - make sure that you have the correct :target on the load, see com.fulcrologic.fulcro.algorithms.data-targeting, e.g. targeting/append-to.

      4. The data is at the wrong place in the DB - likely a wrong ident

    2. If the source of the wrong/missing data is Initial State:

      1. Is your syntax for the :initial-state correct (template x lambda mode)? Make sure not to mix up the two in the same component! Example:

        ;; given :query [.. {:my/child SomeChild}]
        ;; 1. template mode (preferred)
        :initial-state {:my/static 1
                        :my/dyn :param/dyn-input (1)
                        :my/child {}} (2)
        ;; 2. lambda mode w/ the same meaning:
        :initial-state (fn [params]
                         {:my/static 1
                           :my/dyn (:dyn-input params)
                           :my/child (comp/get-initial-state SomeChild)})
        1Values with the magical ns :param are replaced with the value of the corresponding, namespace-less key in the input params provided to the initial state by the parent
        2For keys that match a join in the query, a map value is replaced with the initial state of that sub-component and the map is passed to it as its params
        1. Try (comp/get-initial-state YourComponent) and see whether it looks OK or not

      2. Is the initial state composed correctly to the parent’s and all the way up to the root component? Is the component’s query also composed into the parent’s query?

        Example
        (defsc Child [_ {:child/keys [id some-prop]}]
          {:ident :child/id
           :query [:child/id :child/some-prop]
           :initial-state {:child/id :param/id}}
          ..)
        
        (defsc Parent [_ {child :parent/child}]
          {;:ident ...
           :query [:parent/child (comp/get-query Child)]
           :initial-state {:parent/child {:id "123"}}
          (ui-child child))
        1. Try (comp/get-initial-state Root) and check the result, explore any "broken links"

  3. Data in DB OK but props in the UI are nil / wrong

    1. Is ident/query/initial-state and propagation up to the Root correct?

      1. Check the transformation of the DB to the props tree manually:

        (let [state (app/current-state APP)]
            (com.fulcrologic.fulcro.algorithms.denormalize/db->tree
              (comp/get-query Root) ; or any component
              ;; Starting entity, state itself for Root
              ;; otherwise st. like (get-in state-map [:thing/id 1]):
              state
              state))

        If the resulting data tree lacks the props at the expected place even though the source data is in the client DB:

        1. You are likely missing a correct join somewhere, i.e. not composing a child component to its parent correctly. Try (comp/get-query Root) and check:

          1. Is every expected join present?

          2. Is every present join connected to a component, i.e. was its query produced by (comp/get-query SomeChild)? Check (→ some-join vals first meta). A way to show all the joins, annotating each with the component or :NO-COMPONENT:

            (require '[clojure.walk :as w])
              (w/postwalk
                (fn [x]
                  (cond
                    (map-entry? x) x
                    (vector? x) (list
                                  (get-in (meta x)
                                          [:component :displayName]
                                          :NO-COMPONENT)
                                  (filterv map? x))
            
                    :default x))
                (comp/get-query Root))
        2. Is a join ignored despite having data because the component in question lacks data (:initial-state) of its own? This can happen e.g. to components that only have Link Queries in its query and not state of their own, as described in 6.8. A Warning About Ident and Link Queries:

          The problem is that the query engine walks the database and query in tandem. When it sees a join [..] it goes looking for an entry in the database at the current location [..] to process the subquery against. If it finds an ident it follows it and processes the subquery. If it is a map it uses that to fulfill the subquery. If it is a vector then it processes the subquery against every entry. But if it is missing then it stops.

    2. Is the data tree correct but the target component is not refreshed?

      1. Try rendering manually: (app/force-root-render! com.example.client/app) If it helps then the problem is indeed that somehow you fell out of the normal rendering process. Look below for possible causes.

      2. Do you use other than the default :optimized-render! (i.e. multiple-roots-renderer as of Fulcro 3.4, which is essentially keyframe-render2 + floating roots and does always render from the Root), possibly one that tries to optimize away "unnecessary" renders, such as the ident-optimized-render?

        1. Try switching to the simpler keyframe-render2, which renders from Root, including all ancestors, when you create your APP: …​ :optimized-render! com.fulcrologic.fulcro.rendering.keyframe-render2/render!

      3. Do you use transact!!? It only refreshes the component passed to it (or nothing, if you passed the app itself).

    3. Routing - if a dynamic router is involved, it is possible that you have made a mistake causing it to point to a different target component than you expect. Check the path :com.fulcrologic.fulcro.routing.dynamic-routing/id <router> :current-route in the db

  4. Forms not working properly

  5. Have you messed up with the default behavior?

    1. Did you override the tx processing?

    2. Are you using a non-default rendering optimization plugin?

  6. Assorted troubleshooting tips

    1. UISM

      1. Make sure not to use transact![!]; use only apply-action and trigger-remote-mutation

Backend (Pathom)

The key concept and source of problems are resolvers. Is there a resolver for the property you are interested in? Are there all the necessary resolvers for getting from the ID property A you have to the property P you are interested in? Does the resolver work? Even if some of the resolvers are auto-generated e.g. by Fulcro RAD, you still end up just with resolvers and properties.

Note: I use the same namespace aliases as Pathom documentation (primarily com.wsscode.pathom.core :as p, com.wsscode.pathom.connect :as pc).

Expand All (Click to expand, double click to collapse.)

  1. Query does not return the expected data

    1. Is your EQL syntax correct? The basic syntax is [:thing/simple-property {:thing/join-property [:joined-thing/prop1]}] though it might get more complicated with parametrized properties / idents / joins, union queries etc. (Remember that a join always looks the same and returns either a single map or a vector of maps depending on whether it is 1:1 or 1:many.)

      1. Check your query against the Spec (though beware - the spec does not need to be perfect and might possibly accept some invalid corner cases, I imagine):

        (require 'clojure.spec.alpha 'edn-query-language.core)
        (clojure.spec.alpha/explain
            :edn-query-language.core/query
            [:your.query/here ...])
        ; OUT: Success!
    2. Run the query manually through the parser:

      (com.example.components.parser/parser
          {} ; env plugins such as RAD's pathom-plugin
             ; will add necessary stuff here
          [:your/query :is/here ...])
      1. Remember that Pathom environment is a map of configuration and context, typically containing a data source / connection. It is continually enhanced through the resolution process, which resembles a recursive, depth-first search, where the output of one resolver is added to the env before the next resolver for a join is invoked. Given the query [{[:student/id 1] [:student/github {:student/classes [:room/nr]}]}]: first the student resolver sets #:student{:id 1 :fname "Jo" :classes [[:class/id "X"]]} as the current entity in the env then the classes resolver sets the current entity to #:class{id "X", :room [:room/id 7]} then the room resolver outputs #:room{:id 7 :nr "201"} and the :nr is propagated into the result. Then means that you can invoke any resolver directly through the parser by setting env to contain the relevant context via ::p/entity, you do not need to go all the way from the top. So for example in the fulcro-rad-demo you can run this in parser.clj:

        (parser
            {::p/entity
             #:account{:id #uuid "ffffffff-ffff-ffff-ffff-000000000100",
                       :name "Tony"}}
            [:account/name])
        ; => #:account{:name "Tony"}
    3. Is there a resolver for the property/join in question? (Example)

      1. Look into Fulcro Inspect’s Index Explorer, which lists all known properties and resolvers

        1. If missing: have you created the resolver? Have you registered it with Pathom? (It must, directly or indirectly, be included into the list of resolvers that you pass to p/parser via ::p/plugins → pc/connect-plugin → ::pc/register (RAD takes a vector of resolvers and registers them with Pathom for you))

    4. Run the resolver manually (whether defined via pc/defattr or a Fulcro defattr's ao/pc-resolve), passing it whatever it needs:

      (ns example
        (:require
          ;; SQL:
          [com.fulcrologic.rad.database-adapters.sql :as sql]
          [com.example.components.connection-pools :as pools]
          ;; Datomic:
          [com.fulcrologic.rad.database-adapters.datomic :as datomic]
          [com.example.components.datomic :as cd]
          [datomic.api :as d]))
      (defn datomic-env []
        {::datomic/databases
         {:production (atom (d/db (:main cd/datomic-connections)))}})
      (defn sql-env []
        {::sql/connection-pools pools/connection-pools})
      ;; given (pc/defresolver MyThing) or (defattr MyThing):
      ((:com.wsscode.pathom.connect/resolve MyThing)
        datomic-env input) ; or sql-env if using SQL instead of Datomic
      ;; where datomic-env, input are maps with whatever the resolver needs
      ;; (here we assume a fulcro-rad-demo resolver that needs DB access)

      (The example above is from the Fulcro RAD Demo backed by Datomic. The environment you need to construct is what your resolvers need. The environment is as prepared by the datomic/pathom-plugin, which is registered with the parser. Also see this PR exposing a mock resolver env for Datomic and SQL.)

      1. Is the output in the expected form? (A map or a vector of maps with the properties declared in ::pc/output.)

      2. Has the resolver failed? Check the returned exception / server log

      3. No data returned - capture the arguments, call and troubleshoot the underlying data source directly

      4. Remember: Reload namespaces, restart Fulcro after any change to a resolver, RAD attribute, and registration of resolvers. defresolver and defattr don’t do anything, they just def-ine a map. You need to register them with Pathom.

    5. Is it actually possible to get from the join property to the property you want to obtain?

      1. Run a simpler query. Leverage joins on idents to zoom in on the part of a complex, nested query that is giving you troubles. Example: from [{:all-sessions [:session/id {:session/tags [:tag/id :tag/name]}]}][{[:session/id 123] [:session/id {:session/tags [:tag/id :tag/name]}]}][{[:tag/id 456] [:tag/name]}]. If you are trying to go from property A to D through a chain if implicit connections, focus on a single connection at a time, i.e. first on the property B directly resolvable from A: [{:A 123} [:B]] then on the next connection: [{:B 456} [:C]] etc.

      2. Look into the Index Explorer at the target property and its Reach via, look at the Graph view (tip: look only at Direct inputs and increase the depth to see what the property is reachable from)

  2. TODO

    1. Routing

      1. Basic troubleshooting:

        1. Did you call change-route[-relative]!, including the full path to a leaf target?

        2. Is the router’s initial state composed to its parent’s state, e.g. using :pre-merge if the parent is loaded dynamically?)

      2. Deferred routes

      3. Nested routers

        1. Beware: You must route explicitly to a leaf target at least when it is to be displayed for the first time. Later you can (?) route just to an ancestor target (but it might be safer to always route to a leaf).

      4. ??? Timeout → router believes that current-route is the default but the UI shows the one you expect (with no props)

      5. TODO: "dr/target-ready! was called but there was no router waiting for the target listed: [:component/id :com.example.ui/Accounts] This could mean you sent one ident, and indicated ready on another."

    2. UISM

      1. Note: UISM should be pure and not use transact! and similar. Use trigger-remote-mutation etc.

    3. Mutations

Fulcro RAD

Fulcro RAD is just an addon to Fulcro that builds on its established base, there is no magic. Follow the standard Fulcro troubleshooting guide above. Only look here for issues unique to RAD.

defsc-form and defsc-report or still just defsc components (and so is Fulcro’s defrouter).

defattr only defines a map, that must be passed to a form/report, may contain an inline defresolver via ao/pc-resolve, and are used to auto-generate resolvers from the ao/identity? true attribute to those that have it in its ao/identities set.

  • RAD report parameters <> controls <> the resolver - to influence what the report shows, you can supply additional parameters to the global resolver serving it. These come from the report’s controls and can be supplied to the report via route params when routing to it (to display it). You need to make sure that they match. Example:

    ;; In the model, we use `:conference/uuid` to limit the output:
    ;; (`query-params` is extracted from the query AST and put in env by a RAD plugin)
    (defresolver all-talks [{:keys [query-params] :as env} _]
    {::pc/output [{:talks [:talk/id]}]}
    {:talks (get-talks (:conference/uuid query-params))))
    
    ;; In the report, we have a control with the `:conference/uuid`  id
    (defsc-report TalksReport [this props]
      {ro/controls {:conference/uuid {:type :uuid, ...}}
       ...})
    
    ;; When displaying the report, we provide the `:conference/uuid`
    (rad.route/route-to! app TalksReport {:conference/uuid uuid})

Common sources of error

Demo of a troubleshooting process

Let’s go together through an example troubleshooting process. This fulcro-rad-demo is modified to show an uppercase label on the Inventory Report but it is broken. No label is displayed. This are the most relevant parts of the code:

item_forms.cljc and model.item.cljc
;; The modified report:
(report/defsc-report InventoryReport [this props]
  {ro/title   "Inventory Report"
   ro/columns [item/item-name category/label-uppercase item/price ...]
   ...})

;; And the new attribute is defined as:
(defattr label-uppercase :category/label-uppercase :string
  {ro/column-heading "Category*"
   ...})

Since in Fulcro view = function(data), the first question to ask is:

Question 1: Is the label data in the client DB missing / wrong?

And, as we see in the screenshot below (together with the broken report), the data is missing from the DB:

demo1 ui db missing label
Figure 1. Inventory Report with a missing "Category*" and Fulcro Inspect’s DB Explorer for a report row item

Note: You find report row data in the DB via :com.fulcrologic.rad.report/id:com.example.ui.item-forms/InventoryReport:ui/current-rows, which is a list of :item/id idents. The DB Explorer is little more useful here as you can click an ident there to get to its details while in the DB view you need to navigate to it manually.

I expected to see something like this, with the :category/label-uppercase property present in the row item:

demo2 expected ui db
Figure 2. Inspect’s DB view of a report row item with the defect fixed

Conclusion 1: The problem isn’t in DB → UI but in getting the data into the client DB, i.e. either I failed to issue the load from the backend → client DB or it is not doing what is expected. Since we see other item data present, the most likely cause is that the load is happening but it is not doing all of what I expect.

Question 2: I am exploring load! - missing data - Has a load actually been issued? I assume it has because most of the data is present but let’s anyway check what is happening in the application by exploring its Transactions log:

demo3 inspect transactions
Figure 3. Inspect’s Transactions log

Conclusion 2: A LOAD for the report is issued (as I expected) and it correctly asks for the missing property.

Let’s explore the load in more detail using the Network tab.

Question 3: Is there a problem with the query / backend? What is the response from the server? (I click the request of interest to see its response.)

demo4 inspect network
Figure 4. Inspect’s Network tab with a request and the response

Conclusion 3: The request looks correct, the response lacks the property.

But to see the response as is, before any filtering, we want to use the [Send to query] button and use the EQL tab to learn more (and possibly to simplify and tune the query to learn more about the problem).

Question 4: What does the raw response look like? Can I simplify it?

demo5 inspect EQL

(I use [Run EQL] to send the query to the server and I can [(Re)load Pathom Index] to get auto-completion of known properties.)

Conclusion 4: We see that the response actually has :category/label-uppercase but the value is ::p/not-found, which would indicate that Pathom does not know the property we are asking for. Let’s verify it.

I have gone through this Frontend (Fulcro) troubleshooting:

  • Data in the client DB missing / wrong

    • load! from the server

      • Missing data

        • Has a load actually been issued?

        • Is there a problem with the query / backend?

I can now proceed with troubleshooting as described under Backend (Pathom):

  • Query does not return the expected data

    • Is your EQL syntax correct? - no reason to suspect that it isn’t

    • Run the query manually through the parser - I don’t think that would bring any new insight or any useful simplification

    • Is there a resolver for the property/join in question? - let’s look into this one!

Question 5: Is there a resolver for the property/join in question? I.e. does Pathom know about the property (or Pathom "data attribute", not to be confused with RAD’s defattr attribute) I’m requesting, :category/label-uppercase?

demo6a inspect IndexExplorer 1 unknown
Figure 5. Inspect’s Index Explorer list of known (Pathom, not RAD) "data attributes" a.k.a. properties

Conclusion 5: :category/label-uppercase is not in the list of known Pathom properties/attributes, even after I (!) re-loaded the index. So it seems it has not been registered with Pathom correctly.

Let’s have a look where the closely related and known property :category/label comes from:

demo6b inspect IndexExplorer 2 label
Figure 6. Inspect’s Index Explorer - details of a data attribute

OK, so we see it is provided by the category/id-resolver. This is a RAD-generated resolver from the :category/id attribute marked with ao/identity? true to any other attribute that has it in its ao/identities and has the same ao/schema. Let’s look at the resolver itself:

demo6c inspect IndexExplorer 3 resolver
Figure 7. Inspect’s Index Explorer - details of a resolver

We can see that it produces a single property, :category/label. Why?

Question 6: Why doesn’t Pathom know about my :category/label-uppercase attribute? Is it not defined or not registered with Pathom?

When I look at the code, I can see that this attribute is actually not expected to be provided by the id-resolver because it is not in the backend DB, it is a virtual attribute with its own resolver. (If it was a physical one, it would need both ao/identities #{:category/id} and ao/schema :production just as the :category/label attribute, to match the id attribute.) So why isn’t the label-uppercase resolver known to Pathom? The most logical answer is:

Conclusion 6: The attribute is defined but it is not registered with Pathom. And we can see that this is indeed the case, as it is missing from the attributes vector below (which is eventually composed to the resolvers passed to the Pathom parser):

demo7 c e m category
Figure 8. Code defect discovered: missing registration of the attribute

After we fix the defect, restart Fulcro, and (re-)Load the index, we will be able to see both the :category/label-uppercase-resolver and the :category/label-uppercase data attribute in the Index Explorer, as the screenshot below shows. It also shows the Graph View of the data attribute with 2 levels of direct inputs so that we can see that Pathom is able to go from :item/id:category/id:category/label:category/label-uppercase as well as go directly from :item/id:category/label (this latter thanks to our manual item-category-resolver).

demo8 inspect IndexExplorer fixed attr graph
Figure 9. Inspect’s Index Explorer after the fix - graph view of the data attribute

Still in need of help?

If you need a programming buddy to help you with your problem and pair-program a solution and are willing to pay for my time, you can find me at Codementor:

Contact me on Codementor

If you enjoy Fulcro and Fulcro RAD, consider supporting the author Tony Kay at GH Sponsors.


Tags: Fulcro troubleshooting ClojureScript

Are you benefitting from my writing? Consider buying me a coffee or supporting my work via GitHub Sponsors. Thank you! You can also book me for a mentoring / pair-programming session via Codementor or (cheaper) email.


Copyright © 2021 Jakub Holý
Powered by Cryogen
Theme by KingMob