What’s the recommended pattern on how to steer statecharts? Currently in transition elements I usually have a script and that script does some decision logic and calls senv/raise . Do you do the same or do you like to roll it out more explicitly, mutating working memory and then using If elements to inspect sc state and raise elements to switch states? I think I might be using charts too thinly, lots of logic is in script functions so that’s opaque from chart perspective.
Should I try to use scripts more to transfer/extract data from scf/mutation-result to working memory and then run elements on that?
in general mutations should use returning and target so that there’s no logic to do on that front. If there’s a single fact that you can point to that represents something, then duplicating that fact as a state is generally a bad idea, but in general I guess the way I think about them is “what is the process that I need to track that I would otherwise spread out in callback hell or hairballs of dijointed logic? It’s and organizational tool. I want to see what my system does, not have to puzzle it together. So, in my subscription system for example, I’m keeping track of things like “fully paid” because otherwise I have to analyze invoices and payments. I track grace periods on late payments with a state and a delayed event, etc. The payment processing gateway (which is disconnected mess of UI, server db interactions, webhooks, etc.) needs a few states and timed events, etc. But when you get too low into the weeds you may be making more in the statechart than is necessary, or might be easily represented by a simple, say, ui flag.
The reason I never write generic form or report logic at all anymore is because all of the generic process logic for what you do in those is easy to write-once as an abstraction over the the data integration of Fulcro itself already has.
Which IMO is one of the cooler things that “popped out” of the overall design (UI State Machines and now those same things applied to statecharts).
Thanks for explanation.
I’ve got a chart that runs a process and different callers start it. I need a callback like mechanism so the code that started it can do some refreshes when the chart ends, but it’s different every time based on the context of the caller. How do I most easily achieve that?
This is what invocations are about. Implement the InvocationProcessor and install it.
do you mean to write my own InvocationProcessor that supports a callback?
Not exactly. Invocations are exactly what they sound like, but the communicate via events, and the protocol is the adapter. See SCXLM specification and statecharts docs.
callbacks are exactly what you want to avoid, right?
you want events coming into the context of the chart, where they can be handled by the chart
I mean yea I know how statecharts communicate, but I am starting this one outside of any statecharts, so there’s nothing to get back
you also want a “called thing” to terminate when you’re no longer interested in that call running
I mean usually if you are already in sc, you start a chart and you get events back or whatever. But if I am not in sc?
What?? You mean you have an external process that needs to talk to the chart? It sends events to the session id via the external event queue
which it can take as a parameter when you start it
no it’s just a on-click handler, starts a chart with scf/start!, at some point that process completes, and I want to run some refresh operations. The precise nature of operations is known at the point of this scf/start! call rather than in the chart context. i.e. in another context a different thing needs to happen when this chart completes
maybe I’m coming at this wrong
Personally I’m constructing my UI completely from statecharts. In your case you’re using them more selectively, so you need to invent your glue point. You could certainly pass a lambda into the initial data of the chart on start.
hm I guess lambda works even though it’s not serializable?
Are you serializing your charts or state?
Certainly if you were using a support viewer or including them on mutation params you’d have a problem…
not at this time, I am just staying conservative in case I need this later. Do you put things like lambdas in your statecharts?
other than as expressions for script elements? Not generally. I tend to follow the “serializable” rule because things like working memory in my sever-side statecharts gets written to SQL db when “idle” (between external events). So, no, I don’t tend to store anything but data in my events/working memory…but remember that EQL (which includes mutations) are themselves data. So, putting a mutation with params as data (a vector containing a list that starts with a symbol…) is the Fulcro equivalent of storing a ” serializable callback” which can be invoked via transact!
Some advice on idents… Most UI components I’ve got have idents like:
[:component/id ::Table]
But the backend resolvers have keys like:
::user-list
So pretty much all of the time I have to do the targetted load where I specify UI ident, then the server ident, then the target vector of where to put the thing. Seems kinda redundant. Does anyone have a different approach that would be more ergonomic?I use:
{
;; Always use 2 spaces for indentation (equivalent to "Default to Only Indent")
;; This applies [:inner 0] to all forms, giving consistent 2-space indentation
:indents ^:replace {#".*" [[:inner 0]]}
:align-map-columns? true
:indentation? true
:remove-surrounding-whitespace? true
:remove-trailing-whitespace? true
:insert-missing-whitespace? true
:remove-consecutive-blank-lines? false
}unfortunately not everyone on the team agrees with my flat 2 space indent
I used to run that too
You could make a pathom resolver that treats certain kinds of idents reflectively (e.g. :component/id) and just do load with that ident as the server key
(df/load! this [:component/id ::Table] Table)or even write a wrapper to make it even shorter:
(defn load-component! [this cls]
(df/load! this (comp/get-ident cls) cls))or using the statecharts primitives if you’re in there
I guess this could work
or you could handle the ident case without the pathom resolver with a wrapper as well, so less invasive
(defn load-global-as-field! [this cls field]
(df/load! this field cls {:target (conj (get-ident cls) field)}))This is why I didn’t go (more) nuts on the load system. In fact, if I had it to do over again I’d have started with statecharts and load would not have post-action, post-mutations, etc.
Well I liked the idea that I could issue a load of the whole page and the query abstraction would automatically result in an EQL query that I could process on the server, but this split between the query on the client and the the query on the server kinda kills it. So I just issue loads for individual bits of data when I need it, but now this isn’t much different than REST APIs
ask Claude?
this should work?
(afop/load ::second-fa/credentials :actor/list {:target [:actor/list ::second-fa/credentials]})
I would think that actor could be used in target vector like this, but I get this warnings:
2026-06-04T21:44:56.856Z WARN [com.fulcrologic.fulcro.data-fetch:108] - Data load targets of two elements imply that you are targeting a table entry. That is probably incorrect. Normalization targets tables. Targeting is for creating missing edges, which are usually 3-tuples. See look at the docs/source…don’t remember. In UISM I made separate arg names for ones that support actors to prevent accidents, but don’t remember to be honest