yup, I'm (slowly) pulling one together! Interesting finding though - it will be very hard to have running examples in any browser based example material - defsc is very heavily based on clj code in the macros, making sci a very hard to use method of getting editable running examples.
Does anyone have strategies for dealing with large amounts of state / components in fulcro? I'm on my 2nd large active page, (4.5kloc) and it is becoming spaghetti again 😐.
I think the cause is me not fully matching how I code style I write in to how fulcro thinks. I end up with a bunch of crosstalk between elements, which is sub-optimal.
Hey! Can you give an example of that crosstalk?
The trivial answer is to make a resolver on an invented property name on the back-end. The alternative is more complicated, but there are many alternatives. Is the invented propery like :project/sample-todo-labels
where the resolver sends back a comma-separated string not good enough?
The main alts are: • Custom BodyItem • Row query inclusions (alternate to prior, cannot be used together) with a custom column renderer.
I'll try 🙂.
One e.g. is this pic. At the top you can pick a crop (Cotton here). Right below that input you can see some inputs that have the lbs
label - lbs
are the unit of cotton. That unit should also appear in Farm Stress Test
under Low/High yield. Those are pretty distant parts of the app the way it's currently structured.
I will say that the only viable idea I can think of is some type of interface file that defines all possible operations on the page, and allow that file to properly delegate all the possible user interactions. Not tried it yet, but I might if I can't come up with better ideas 🙂.
have you considered making the controls at top have a constant ident, and then querying for the controls in the other spots? From my perspective you have a tree that contains all this stuff. The shared values could be passed manually from top to bottom, but you can also say [{[:control :thing] […]}]
in any component query and pull in the current values of the controls.
That introduces some coupling (children have to know ident of parent controls), which is where I’d tend to lean more in the direction of passing them down as computed.
hard to say more without really delving into your code
Yes-ish. I do that already to some extent. However, the particular issue here is the Low/High yield here must have data altered in the db. That unit flag is part of the data, so changing the crop must change the unit across the page in the db.
Has anyone figured out a way to make guardrails check the results of an async function? For example a function that returns a channel that will receive a string? Would be super cool if I could do
(>defn foo [] [=> (async string?)]
(async/go "hello world!"))
You could wrap the returned channel in another and check as you move the data from the inner to the outer as proposed but you would perhaps also want to make sure that if the wrapper channel is closed, you close the inner one and vice versa. Not trivial to do everything right.
that channel wrapping is tricky, because you cant know the characteristics of the channel you are wrapping, how to follow buffer type/size? is the channel a promise channel?
This is the kind of thing copilot could be taught to do (if I ever get it done). There’s some limited support a static checker like kondo could do as well. Either way this is probably more tractable with a source-level analysis than a runtime check. You could invent your own channel wrappers that do data flow checking instead.
Hi @tony.kay I think the invented property name you suggested would work in most cases. Does the invented property need to have an associated attribute defined in the model or is defining a resolver sufficient for this approach?
For learning purposes, I’m curious about how custom BodyItem and row query inclusions would work. Is there some example code somewhere that implements that? Thanks for the info!
You’ll need an attr in order to add it as a column
in CLJC, you can co-locate the resolution with the attr
On the other: Reports have to have a component for the report rows. The macro, by default, generates one (with -Row
as a suffix to your report component’s name). That generated row component has the query for the cols + row query inclusions and an ident based on the id attribute of the row. If you specify BodyItem then you’re making the row component. It’s just a standard Fulcro component. You specify the ident, query, etc. From there the report component will just pass you props for the row, which you can render, or you can call the helper for row rendering the that plugin uses.
The macro isn’t a ton of code…for educational purposes it is probably worth reading
Nope. It doesn’t return a string. It returns a channel, that happens to have a string on it.
If you put your “hello world!” code into a sync function, and the call that from foo, you’ll get the check where it belongs. For GR to check it your way, it would have to be pulling items off channels and shoving them back on, or some other mess. Possible, but not present.
(e.g. it could instrument the body and put a channel in-between your returned channel and what it actually returns)
Ok - that makes sense. Thanks. What is the name of the macro that generates the -Row
component?
yeah that last suggestion was what i was thinking...
messy indeed
but could work, and guardrails is pluggable
pluggable in what way?
I wrote the macro and don’t remember leaving this kind of pluggable 😄
defsc-report
😄
:defn-macro
it generates your report component, and if you don’t tell it the row component (BodyItem), it generates that too
Oh, I see. You mean do it as a wrapper…yeah, that’s perhaps a bit messy way of doing it
is there a cleaner way?
how else would i take the result from an async channel, validate it, then put it "back"
how is a wrapper going to work? Remember that macros eval opposite to function calls. Your wrapper would be applied to the “raw” output fn
not as an intermediate transform
I’ve not tried, so I don’t know for sure, but I suspect the only way to do it is to actually patch the >defn
macro itself
i was thinking wrap the body in a go block, take the value from the body, run spec checks on it, return the value at the end of the go block
that only works if i can get a handle to the specs in my macro body
right…not sure they’re there. You can look/try 🙂
I’m open to a PR that makes this sort of thing possible, but since I’m also working on the Copilot stuff which uses this same macro it would have to not interfere with that
i just finished writing a bunch of async crap, next time i have to do this sort of thing, i might try a quick spike
re: defsc-report
Doh! I probably could have guessed that one..🤦
@tony.kay @alex-eberts @holyjak @slack1304 thank you all for all kind suggestions! I'll take the absolute beginner route using the video with the Chapter 4 as a companion. (I did try to read the chapters 1-3 quickly, the only thing that I learned is that I got to come back to digest the concepts, starting from Chapter 2 "Fulcro From 10,000 Feet", my impression got fuzzier. Chapter 3 "Core concepts" seems also to hard to digest for me at the moment. It seems to me that these two chapters could be a book of its own, so abstract, and sounds very wisdom.
It may seem a little daunting at first but it does get easier! As Tony mentioned, spending time up-front understanding the core concepts of queries, indents, initial-state, transact and render will really help in the long run. One thing to keep in mind if you’re coming from Re-Frame is that queries in Fulcro do not act in the same way as subscriptions do in Re-Frame (I thought they were the same thing when I started studying Fulcro and I was confused for days). Re-Frame subscriptions allow each component to grab data from the client-side app database directly (or “Sideband” as Tony would say). Fulcro components get their data via props from their parent (i.e. a Fulcro query is not a “direct” query to the db). The query element in a Fulcro component is actually more like a “query fragment” (see :query in defsc
). Each component declares what fragment of the full data tree it needs via the query. When your app loads, the queries of all the components in the hierarchy are combined and that query is sent to your remote database (via the pathom library). In this way, the “shape” of the response will exactly match your component’s data needs. Hope that helps!
The element picker in fulcro devtools doesn't work for me: I have added the com.fulcrologic.fulcro.inspect.dom-picker-preload preload, restarted shadow, but when I click pick element and click anywhere on the page nothing happens. Anything I should try?
Do you have latest versions of the tools and Fulcro? (I saw it working for other people so it is not broken)
Oh yes, that was it, thank you!
Yes 😢 I looked at that (though not Sci) a way back. Having a server that would compile and return the code is the only solution I found. Perhaps an AWS Lambda or similar with limited throughout (to keep charges negligible)...