fulcro

Book: http://book.fulcrologic.com, Community Resources: https://fulcro-community.github.io/, RAD book at http://book.fulcrologic.com/RAD.html
JAtkins 2021-01-13T00:14:02.337800Z

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.

❤️ 1
JAtkins 2021-01-13T00:20:00.339300Z

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 😐.

JAtkins 2021-01-13T00:22:05.339400Z

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.

hmaurer 2021-01-13T00:36:10.339700Z

Hey! Can you give an example of that crosstalk?

tony.kay 2021-01-13T00:36:13.339900Z

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?

tony.kay 2021-01-13T00:36:47.340100Z

The main alts are: • Custom BodyItem • Row query inclusions (alternate to prior, cannot be used together) with a custom column renderer.

JAtkins 2021-01-13T00:40:54.340300Z

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 🙂.

tony.kay 2021-01-13T00:44:29.340700Z

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.

tony.kay 2021-01-13T00:45:30.340900Z

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.

tony.kay 2021-01-13T00:45:41.341100Z

hard to say more without really delving into your code

JAtkins 2021-01-13T00:46:52.341300Z

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.

currentoor 2021-01-13T01:11:36.343700Z

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!"))

Jakub Holý 2021-01-13T09:50:35.354300Z

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.

wilkerlucio 2021-01-13T12:55:32.357100Z

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?

😱 2
tony.kay 2021-01-13T14:52:54.357800Z

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.

2021-01-13T01:22:18.343800Z

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?

2021-01-13T01:23:55.344Z

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!

tony.kay 2021-01-13T01:24:24.344200Z

You’ll need an attr in order to add it as a column

👍 1
tony.kay 2021-01-13T01:24:38.344400Z

in CLJC, you can co-locate the resolution with the attr

tony.kay 2021-01-13T01:26:22.344600Z

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.

tony.kay 2021-01-13T01:27:11.344900Z

The macro isn’t a ton of code…for educational purposes it is probably worth reading

tony.kay 2021-01-13T01:28:10.345200Z

Nope. It doesn’t return a string. It returns a channel, that happens to have a string on it.

tony.kay 2021-01-13T01:29:22.345500Z

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.

tony.kay 2021-01-13T01:29:54.345700Z

(e.g. it could instrument the body and put a channel in-between your returned channel and what it actually returns)

2021-01-13T01:33:05.345900Z

Ok - that makes sense. Thanks. What is the name of the macro that generates the -Row component?

currentoor 2021-01-13T01:56:29.346100Z

yeah that last suggestion was what i was thinking...

currentoor 2021-01-13T01:56:32.346300Z

messy indeed

currentoor 2021-01-13T01:56:56.346500Z

but could work, and guardrails is pluggable

tony.kay 2021-01-13T02:00:50.346800Z

pluggable in what way?

tony.kay 2021-01-13T02:01:13.347Z

I wrote the macro and don’t remember leaving this kind of pluggable 😄

tony.kay 2021-01-13T02:01:35.347200Z

defsc-report 😄

currentoor 2021-01-13T02:01:50.347400Z

:defn-macro

tony.kay 2021-01-13T02:02:01.347600Z

it generates your report component, and if you don’t tell it the row component (BodyItem), it generates that too

tony.kay 2021-01-13T02:02:27.347800Z

Oh, I see. You mean do it as a wrapper…yeah, that’s perhaps a bit messy way of doing it

currentoor 2021-01-13T02:02:47.348Z

is there a cleaner way?

currentoor 2021-01-13T02:03:21.348200Z

how else would i take the result from an async channel, validate it, then put it "back"

tony.kay 2021-01-13T02:03:23.348400Z

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

tony.kay 2021-01-13T02:03:36.348600Z

not as an intermediate transform

tony.kay 2021-01-13T02:04:07.348800Z

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

currentoor 2021-01-13T02:04:13.349Z

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

currentoor 2021-01-13T02:04:43.349200Z

that only works if i can get a handle to the specs in my macro body

tony.kay 2021-01-13T02:04:54.349400Z

right…not sure they’re there. You can look/try 🙂

tony.kay 2021-01-13T02:05:39.349600Z

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

currentoor 2021-01-13T02:06:54.349800Z

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

2021-01-13T02:57:18.350Z

re: defsc-report Doh! I probably could have guessed that one..🤦

yubrshen 2021-01-13T05:01:20.351300Z

@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.

2021-01-13T05:57:43.351800Z

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!

❤️ 1
🙏 1
Gleb Posobin 2021-01-13T07:04:59.352900Z

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?

Jakub Holý 2021-01-13T08:14:48.353900Z

Do you have latest versions of the tools and Fulcro? (I saw it working for other people so it is not broken)

Gleb Posobin 2021-01-13T14:33:24.357600Z

Oh yes, that was it, thank you!

1
Jakub Holý 2021-01-13T07:48:02.353700Z

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)...