Fulcro Troubleshooting Decision Tree
- Key troubleshooting insight from Fulcro’s author
- The key to troubleshooting
- Troubleshooting steps by problem
- Common sources of error
- Demo of a troubleshooting process
- Related resources
- 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 occasional development and is updated irregularly. |
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.
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 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 experimentationUse 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.
Note: I use the same namespace aliases as Fulcro documentation.
Expand All (Click to expand, double click to collapse.)
Fulcro Inspect not working properly
DB and DB Explorer does not work / shows no data
Try:
Restart Chrome Dev Tools
Re-install the Chrome extension
Use the standalone Inspect Electron app instead
Check the error logs of the extension and of Fulcro itself
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)
EQL: No autocompletion → [(Re)load Pathom Index] (the button to the very right)
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
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:
If the source of the missing/wrong data is
load!
from the server:Missing data
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.
Is there a problem with the query / backend? (Example)
Find the request in Fulcro Inspect’s Network tabs and click [Send to query] (screenshot).
Does the query look ok? Does it include what you expect?
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
The data is loaded but not normalized correctly
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.)
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, seecom.fulcrologic.fulcro.algorithms.data-targeting
, e.g.targeting/append-to
.The data is at the wrong place in the DB - likely a wrong ident
If the source of the wrong/missing data is Initial State:
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)})
1 Values with the magical ns :param
are replaced with the value of the corresponding, namespace-less key in the inputparams
provided to the initial state by the parent2 For 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 Try
(comp/get-initial-state YourComponent)
and see whether it looks OK or not
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))
Try
(comp/get-initial-state Root)
and check the result, explore any "broken links"
Data in DB OK but props in the UI are nil / wrong
Is ident/query/initial-state and propagation up to the Root correct?
Check the transformation of the DB to the props tree manually (🎥 tip: the 4 min video Turning Root query into props tree demonstrates a useful technique for analyzing the issue using Fulcro Inspect):
(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:
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:Is every expected join present?
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))
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.
Is the data tree correct but the target component is not refreshed?
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.Do you use other than the default
:optimized-render!
(i.e.multiple-roots-renderer
as of Fulcro 3.4, which is essentiallykeyframe-render2
+ floating roots and does always render from the Root), possibly one that tries to optimize away "unnecessary" renders, such as theident-optimized-render
?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!
Do you use
transact!!
? It only refreshes the component passed to it (or nothing, if you passed the app itself).
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
Forms not working properly
Have you added form config via
fs/add-form-config
to the entity?Are idents correct?
Have you messed up with the default behavior?
Did you override the tx processing?
Are you using a non-default rendering optimization plugin?
Area-specific troubleshooting tips: UISM, routing
UISM
Make sure not to use
transact![!]
; use onlyapply-action
andtrigger-remote-mutation
Dynamic Routing and Routers
Basic troubleshooting:
:will-enter
is called multiple times - that is normal; if you don’t want that then usedr/route-deferred
and put your code inside its callbackDid you call
change-route[-relative]!
, including the full path to a leaf target?Routing transactions and logs look OK but the UI does not show the expected target
Is the router’s initial state composed to its parent’s state, e.g. using
:pre-merge
if the parent is loaded dynamically?)
Deferred routes
Nested routers
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).
??? Timeout → router believes that current-route is the default but the UI shows the one you expect (with no props)
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."
Frontend <-> Backend communication
Expand All (Click to expand, double click to collapse.)
Data from my resolver does not make it into the Client DB
Does the resolver produce the data? Add a log or something to it to verify it
Is the data sent by the backend? In browser Dev Tools, look at the response’s body in the Network tab (not Fulcro Inspect’s!). It is transit and thus little harder to read, Michiel Borkent’s Jet (
cat <json> | jet --from transit --to edn
) or Fulcro’s transit-str→clj can help.Yes ⇒ read 1.c below to troubleshoot the frontend
No and your resolver is producing the data ⇒ follow the instructions for 1.c below to find out whether adding the fields to the query fixes the problem. If it does not, look into your Pathom parser configuration and plugins to figure out where the data disappears.
Backend sends the data (verified above) but they are not visible in the frontend
Fulcro will omit any keys that you do not query for (as well as, in same cases, Pathom itself). To verify, send the query to the EQL tab from Fulcro Inspect's Network tab - [Send to query] then add the missing fields to it. If the response shows them then either modify the original query or add a Fulcro transform to add the fields to the query automatically. See this example of leveraging Fulcro’s
:global-eql-transform
to query for::p/errors
and the RAD’s global-eql-transform adding both ::p/errors and :tempids.
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.)
Query does not return the expected data
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.)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!
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 ...])
Remember that Pathom
env
ironment 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 theenv
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 inparser.clj
:(parser {::p/entity #:account{:id #uuid "ffffffff-ffff-ffff-ffff-000000000100", :name "Tony"}} [:account/name]) ; => #:account{:name "Tony"}
Is there a resolver for the property/join in question? (Example)
Look into Fulcro Inspect’s Index Explorer, which lists all known properties and resolvers
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))
Run the resolver manually (whether defined via
pc/defattr
or a Fulcrodefattr
'sao/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.)Is the output in the expected form? (A map or a vector of maps with the properties declared in
::pc/output
.)Has the resolver failed? Check the returned exception / server log
No data returned - capture the arguments, call and troubleshoot the underlying data source directly
Remember: Reload namespaces, restart Fulcro after any change to a resolver, RAD attribute, and registration of resolvers.
defresolver
anddefattr
don’t do anything, they justdef
-ine a map. You need to register them with Pathom.
Is it actually possible to get from the join property to the property you want to obtain?
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.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)
TODO
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
Component’s query (and sometimes initial state) not composed into the parent’s query (initial state)
Pathom resolver defined but not registered with Pathom
RAD attribute defined but not registered with Pathom
Demo of a troubleshooting process
Watch a screencast of this troubleshooting |
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:
;; 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:
I expected to see something like this, with the :category/label-uppercase
property present in the row item:
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:
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.)
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?
(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 serverMissing 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
?
(Notice that for the Index Explorer’s [Load index] to work, you need to expose the index via a dedicated resolver :com.wsscode.pathom.viz.index-explorer/id
→ :com.wsscode.pathom.viz.index-explorer/index
, registered with Pathom.)
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:
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:
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):
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
).
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:
If you enjoy Fulcro and Fulcro RAD, consider supporting the author Tony Kay at GH Sponsors.