Fork me on GitHub
#fulcro
<
2020-04-01
>
pithyless04:04:04

(defn elision-predicate
  "Returns an elision predicate that will return false if the keyword k is in the blacklist or has the namespace `ui`."
In rad.application - double-checking the code, but pretty sure that doc is meant to be return true

Tuomas05:04:28

Hi a fulcto newbie here. I’m quite frequently getting the “No app connected.” message from fulcro inspect in the devtools although the fulcro popup says “Fulcro app detected! Open Chrome Devtools and look for the Fulcro Inspect tab.” It seems to occur sometimes when refreshing the page. A fresh tab or restarting chrome doesnt fix it. Turning the extension on and off doesnt work eather. The only way I’ve found to fix the issue is close devtools, reinstall extension, refresh page and open devtools. Any idea about what I might be doing wrong / how I could go about fixing the issue?

Jakub Holý (HolyJak)10:04:46

I only see this briefly, after a hard reload of the page, which is expected. But I run my own dev build of the Fulcro inspect (few weeks old)

cjsauer14:04:51

I’ve hit this as well. Re-install was the only fix.

tony.kay16:04:50

Could also be another incompatible chrome extension I guess…but yeah, reinstall will usu fix problems like that. Also make sure your compile has the preload for installing inspect.

dvingo16:04:27

I've run into it when using Brave, but got it work using Chromium

bbss09:04:30

@koivistoinen.tuomas tough one.. Maybe open a devtools of the devtools for any suggestions..

zilti13:04:32

Can, with the RAD datomic backend, an :enum type have cardinality :many?

tony.kay16:04:14

In principle or reality? 😜

tony.kay16:04:47

the intention is for all db back-ends to support to-many scalars like enums. Some of that is not yet implemented.

tony.kay16:04:13

well, and in Datomic enums are technically refs, since enumerated values are technically entities with a named ident.

zilti16:04:12

In reality 😅

tony.kay16:04:15

Yeah, you’ll find it isn’t a lot of code, but there are two things to implement to get that to work right: 1. make sure save-form works with it in db adapter…I think it will. 2. Implement the UI rendering for it, which I’m pretty sure I have not done. I’ve not done rendering for any to-many scalars.

tony.kay16:04:05

On the UI side, I think the layout for forms would have to be made aware of the to-many aspect…that might not be true…it might just be that the enumeration control renderer would have to be made aware. The resolvers on the back end would need to return a vector of values (which might need some adaptation in the resolvers).

tony.kay16:04:33

basically every data type that isn’t a ref should support that, so it is a “turn the crank” sort of exercise.

zilti16:04:17

I see... I might look into this when I actually need that functionality (might still be a bit, right now this project just needs to get off the ground). But on the database side? Will it correctly work in defattr as of today?

tony.kay16:04:36

don’t remember to be honest

tony.kay16:04:53

you can specify custom datomic schema stuff and that will merge last

tony.kay16:04:46

::datomic/attribute-schema {:db/cardinality ...}

tony.kay16:04:58

but I think that is done correctly just from ::attr/cardinality

zilti16:04:38

Well, I guess I will find out when I try, then 😛

tony.kay16:04:45

Also beware that I am making no API stability promises at the moment on RAD. I’m not changing it in major ways, but I am occasionally cleaning up keyword naming for consistency. I may still have to restructure the UI plugin data structure as well (and the rendering algs to go with it), but that should affect things minimally.

tony.kay16:04:11

once I get the full picture realized I’ll stabilize the API and move out of alpha

zilti16:04:10

Thanks, I realize it is in an early state. Might well be that I end up contributing some code, too.

tony.kay16:04:27

that’s always my hope

Alex H14:04:40

What happened to df/load-action and df/remote-load in fulcro 3? The documentation still references those, but they are nowhere to be found in the API docs

tony.kay16:04:39

you can just call load! from mutations now

tony.kay16:04:55

which is simpler…I’ll update the book, missed that

tony.kay16:04:41

fixed and pushed…let me know if something isn’t looking right where you were reading @alex340. I didn’t read terrible carefully

👍 4
lilactown17:04:42

fulcro newbie here. say I have a DB that has a bunch of customers and products in it. is it possible for a component to have a query like:

[{[:customer/id 123]
  [:customer/name
   :customer/email
   {:customer/last-purchase
    [:product/id :product/name :product/price]}]}]
and if so, how do I reconcile a single ident for the component? will it detect that :product/id is also an ident pick up any changes to that in the DB?

wilkerlucio17:04:13

@lilactown in fulcro you would have 2 components there, one for the customer, one for the product, so compose as:

(fc/defsc Product
  [this {::keys []}]
  {:ident :product/id
   :query [:product/id :product/name :product/price]})

(fc/defsc Customer
  [this {::keys []}]
  {:ident :customer/id
   :query [:customer/id
           :customer/name
           :customer/email
           {:customer/last-purchase (fc/get-query Product)}]})

wilkerlucio17:04:37

the trick here is that fc/get-query returns the query with metadata, in the metadata it has {:component Product}

wilkerlucio17:04:07

so when Fulcro is normalizing, it relies on the component on the meta to make the normalization (and figure the :product/id bit)

lilactown18:04:08

gotcha. so your component tree has to roughly map to your query structure

👍 4
tony.kay18:04:41

So, I’ve written this up to explain the problems around DOM inputs (low level) that libraries like Fulcro, React, Redux, etc deal with. I’m interested in getting feedback from anyone that has ideas about how we might do this a little better in Fulcro. At the moment Reagent’s approach works better than Fulcro’s, but I don’t love how hacky it all is. Given that Fulcro really generally wants to use Fulcro-controlled inputs, I think it might be worthwhile to make an input type that is just that. We could have a dom/react-input that would be a completely unwrapped and raw createElement of “input” with a “user beware” warning, leave dom/input alone to ensure we don’t break any existing apps, and perhaps make dom/fulcro-input that has some special abilities so this isn’t such a mess. Would love to hear ideas.

4
tony.kay18:04:08

I just added some rough ideas: https://github.com/fulcrologic/fulcro/blob/develop/src/main/com/fulcrologic/fulcro/dom/DOMInputs.adoc#some-rough-ideas It occurs to me if we had a special fulcro input that could indicate a transaction should be processed syncrhonously, that this problem might evaporate

tony.kay18:04:38

(dom/fulcro-input 
  {:onChange (fn [dom-event update-value!]
               (let [{:person/keys [name]} (comp/transact-sync! this [(m/set-string! this :person/name :event dom-event)]) ; does NOT do rendering or any other dom processing. Just updates state. Returns UI props of `this`.
                 (update-value! name)))
   :value (:person/name props)})

tony.kay18:04:35

could possibly be made even prettier with some wrapping function logic…and we’d probably schedule some kind of debounced render on those in case the input’s value caused other things on screen to update. But that would fix all of the problems I see…well, at least as well as standard React does.

lilactown18:04:12

@tony.kay newbie question, hope you don’t mind, but re- the DOM input: I know that reagent does some hacky stuff due to the fact that it implements an async render queue on top of React. does Fulcro do the same, which is why you want have tighter control over inputs?

tony.kay19:04:43

yeah, has all along. The way I’ve been doing it “works” (mostly), but the other day when I embedded components under a hooks-based component it screwed up, thus my renewed interest

coby19:04:06

> Imagine we could mark a transaction as syncrhonous, and immediately read state so that we’re putting the correct value in the input immediately? I agree that'd be great, but is it possible? From everything I've seen React does not offer this escape hatch, so how would you accomplish that? Granted, I've mostly distanced myself from React announcements etc., to maintain sanity 😛

tony.kay19:04:50

I can do it by not letting go of the thread in the event handler. We’d still be using React’s component local setState, it’s just that we’d be using it synchronously, which is the way it does work.

coby19:04:48

I see. So I did misunderstand. I always got the impression React had moved or is moving entirely to async-only rendering.

coby19:04:07

I guess I'm just misunderstanding the nuance of React internals here. Sometimes (i.e. when controlling inputs via component local state??) it renders synchronously, but rendering is async by default? I guess I'm just confused about how it chooses, and whether it offers that choice via public API.

lilactown19:04:20

the production version of React still uses synchronous rendering

lilactown19:04:01

there’s an experimental async renderer they call “Concurrent Mode” which will become the default at some future point

lilactown19:04:39

Concurrent Mode handles input events by prioritizing it above other state updates

lilactown19:04:17

it basically says, “we gotta run this NOW” and falls back to - if I understand correctly - a sync update

lilactown19:04:42

the main selling point of Concurrent Mode over a userland async render queue is that 1. it’s integrated with all of React’s state/rendering machinery 2. it is a priority queue that allows certain updates to jump ahead of the line to avoid weirdness around async state updates like we see with controlled inputs

lilactown19:04:56

also it integrates with React Fiber to allow pausing and resuming of renders, which allows high priority updates to even interrupt renders currently in progress

coby19:04:26

Gotcha, thanks for the clarification.

lilactown19:04:14

there’s sort of two issues that were called out in the document, tho 1. updating an input outside the frame of an event handler 2. updating an input with some derived value of the actual event doing a transaction synchronously inside the event handler would solve (1), but (2) is tricky even in just plain DOM world and requires handling the cursor position manually AFAICT

tony.kay19:04:03

not true…syncrhonous value would be obtained from state after transaction, so the derived value would be available

tony.kay19:04:21

so that suggested solution would solve both

lilactown19:04:03

unfortunately I still see a cursor jump when calling setState with a derived value like:

setState({value: e.target.value.toUpperCase()});

tony.kay19:04:04

Any non-matching updates coming from other transactions would update through :value, but that value would already match for the sync update

tony.kay19:04:28

OH, right, that case…you’re right

tony.kay19:04:43

:face_vomiting:

💯 8
tony.kay19:04:34

it’s worse, because the transformed value could change lengths

tony.kay19:04:47

imagine a CC input that is adding hyphens

coby19:04:19

I've been grappling with a similar design problem involving contenteditable. Unfortunately what I'm coming up against is that cursor positions and selections are kind of an afterthought in the DOM API. 😞

tony.kay19:04:20

I guess solving (1) cleanly and consistently with rapid UI refreshes fixes 95% of problems. If you do derived stuff, I can point out in dev guide that you’re in DOM land now…have fun!

lilactown19:04:32

yeah. unfortunately, it seems to be how the DOM works: https://codesandbox.io/s/m4r6myo5ny

tony.kay19:04:20

localization makes it even harder…or we could just implement a frikkin DOM control in a canvas and be done with it

coby19:04:50

yes, more yaks plz 😆

tony.kay19:04:57

I mean, the ultimate problem statement is: we want to control the input, but it has mutable state that we don’t actually control…it gets mutated arbitrarily by the browser itself.

tony.kay19:04:46

and if we say “no, we want this”, then we really are mutating the entire value…where else would you put the cursor in a mutable world?

tony.kay20:04:46

Ah, that leads to the observation: maybe we need to treat cursor position as a “controlled” value at all times? Interesting…track keydown/keyup events, and synthesize our own change events?

lilactown20:04:13

that’s the way that people seem to be solving this in React land

coby20:04:23

Yeah, that is my conclusion with this contenteditable stuff as well

tony.kay20:04:36

yeah. the more we talk about it, the more I agree…even though I hate it

lilactown20:04:58

if you are putting derived values into :value , then tracking the cursor position in state and updating it in useLayoutEffect seems to be the best practice

tony.kay20:04:00

I think the sync transaction stuff for fulcro could be a nice performance enhancement, though. And that approach would also fix the weird async behaviors.

💯 4
💪 4
coby20:04:24

I would love it if there were a layer in React or on top of React providing a declarative API for setting cursor position. You'd still be in charge of computing what that position would be after a given transformation, but at least it would take care of all the Selection / Range nonsense

lilactown20:04:59

I think that’s pretty much what a hook would do

lilactown20:04:18

let [value, setValue] = useState("");
let inputRef = useRef(null);
let maintainCursor = useControlledCursor(inputRef);

function onChange(e) {
  setValue(e.target.value.toUpperCase());
  maintainCursor(e);
}

return <input value={value} onChange={onChange} />

lilactown20:04:16

it’s hard to generalize completely though because, like was pointed out before, you might have something like a CC which is going to change your length, and you want to handle that gracefully depending on the context

thosmos20:04:28

is it possible to get cursor position from the dom api? if so would it be possible at write time to read dom value (like Preact?) and cursor position and have a sync callback with these two args allowing decisions around that?

coby20:04:51

Yeah, what I'm imagining is a callback mechanism to make this fully generalizable. Something like:

:compute-cursor-position
(fn [e ref previous-pos]
  (if (something-special-happened)
    42
    (inc previous-pos)))
...and the maintainCursor in the example above takes care of querying/updating the DOM, and also gets passed the ref, and then calls this callback

tony.kay21:04:58

Well, I’ll play with adding the sync transact, and any of you that want to make a fork and contribute a concrete implementation of something I’m open to it.

tony.kay21:04:38

I think Fulcro with sync transact would give us a real leg up over the competition, since it doesn’t have to “break the model” in either direction, and would also not need any crazy cursor management logic.

tony.kay02:04:31

I got it…and it was way simpler than I could have hoped for! Fulcro already has props tunneling through setState! I’ve already got it working perfectly and fast with raw React DOM inputs.

Alex H20:04:51

I guess I could just try - but does it work to have a parameterised join in a :query of a defsc?

Alex H20:04:25

e.g. :query [{(:foo/all {:some-filter "abc"}) (comp/get-query Foo)}]`

Alex H20:04:26

or, maybe more importantly - can I pass something like that as server-property-or-ident` of df/load! ?

Alex H20:04:11

that doesn't seem to work - Assert failed: (or (eql/ident? server-property-or-ident) (keyword? server-property-or-ident))

Alex H20:04:22

(as argument to df/load!)

Alex H21:04:19

so how can I pass EQL parameters to df/load!?

cjmurphy21:04:40

You could use this, borrowed from Fulcro RAD

cjmurphy21:04:47

Add it to the list of ::p/plugins of your parser. Then :query-params is in the env of your resolver.

tony.kay21:04:20

Right…that :params option of load adds EQL params to the top-level of the query, and that plugin will make the params generally available in the sub-resolvers. The nature of resolvers mean you should generally namespace your parameters and place them at the top like this because sub-resolvers may not be called if a top-level resolver can optimize the result in advance. That gives you the most back-end flexibility.