Fork me on GitHub
#fulcro
<
2019-04-18
>
danielstockton08:04:00

How does fulcro solve the issue of queries like [{:join/pointless [{:join/pointless []]] where joins are just there in order for the query to match the UI hierarchy?

claudiu09:04:40

can you give a example of a use-case UI ? Never really had to do this apart from when sending to the server, there I just added a wrapper component just for server interaction to get the shape I wanted and then a post-mutation to merge things back to the ui.

danielstockton09:04:11

Yes @thenonameguy exactly. In om.next that's exactly what I do, give these keys a special namespace and then in call the parser recursively if I meet those keys.

danielstockton09:04:37

The problem is that they also get sent to the server, unless I separate my read query from my load query (which might be what fulcro does?)

danielstockton09:04:13

The problem I see with doing that is that I lose the (perhaps only theoretical) benefit of not caring where my data comes from.

danielstockton09:04:06

It just all seems a bit dirty.

kszabo09:04:04

I’ve not yet used this feature, so can’t comment on the dirty/clean part. To me, it seems okay for a graph resolver to accept that the client wants a certain structure and adding a feature like above to support it. If you want your UI tree to match your data tree I think this is unavoidable

danielstockton09:04:23

This is a pathom feature though. Does that mean that it's the same in fulcro as in om.next? You'll end up sending these placeholders in the load queries and need something like pathom to deal with them server side? I then wonder how fulcro deals with the subsequent merge? Presumably these keys also need to be in the server response to match the initial query, for any normalization to take place.

claudiu09:04:38

In fulcro you can specify to elide certain keys from the server query, or even reformat what is being sent to the backend. The response from the server, if it does not match you can do the merging yourself using the post-mutation

danielstockton10:04:12

I've done similar in om next, passing post mutations as params to a query. As I said above, it feels like you lose the benefit of not caring where the data comes from. You have a UI query and a load query that are different (one matching UI structure and one matching a more natural/backend structure).

danielstockton10:04:35

Also, is it not the case that the app is no longer aware of one query that can hydrate it with all it's data?

claudiu10:04:40

Been wondering about this for a while. Is not knowing/caring about where the data comes from really a benefit ? Maybe I'm just used to it, but having just the app-state and tools like fulcro-inspect makes things really easy to reason about/predict perf issues etc..

claudiu10:04:39

for the hydrate all it's data I usually rely on the router stuff, as if I just navigated to that route.

danielstockton10:04:42

I've found it to be a benefit, because when you come to rewrite a backend, or split into microservices, you often do that incrementally (https://www.martinfowler.com/bliki/StranglerApplication.html). In om.next, I've been able to just change certain parser reads to point to another backend. I have to admit, I'm still familiarizing myself with fulcro and it's not yet clear to me whether loads have to be to a server, or how i specify said server for said loads.

danielstockton10:04:58

Perhaps the fulcro approach would be to do this routing on the backend using something like pathom

claudiu10:04:23

http://book.fulcrologic.com/#_fulcro_and_graphql When you create the client you can specify the :networking. The default is named :remote. For example you can have :rest-api, :remote, :graphql. In the mutation or load you just specify 1 or multiple remotes.

claudiu11:04:23

or at least that's one way 😄 Been using fulcro for a while and constantly find it has a good story & support for about everything. The strangest thing though is the flexibility you have in your approach for all that it offers.

danielstockton11:04:41

When do loads usually take place? Is there a good story for loading everything the app requires in a single request?

danielstockton11:04:24

This is the kind of holistic overview i miss when using something like re-frame, which for all other intents and purposes i find pretty simple to reason about and get stuff done with.

claudiu11:04:34

You get to decide where the load happens. You can put it along side the route change for example http://book.fulcrologic.com/#_combining_routing_with_data_management . For the 1 single request, unless you specify :parallel true in the load, fulcro has a small delay and will try to put the load-requests in a single api call (from the same remote). Ex: issuing (df/load :x) (df/load :y) will be 1 single request in most cases.

danielstockton11:04:04

I see, pretty cool

tony.kay15:04:28

right, that last one: In Om Next the parser in involved with your loads…in Fulcro load is an operation that is triggered with what you want to load from where (so you can name the remote and component)…no rewriting roots, etc. Mutation joins are also integrated so that when you write a defmutation the mutation itself can indicate the remote(s) (as in Om Next) but they can also indicate what component is being returned and where to target that in the graph.

tony.kay15:04:00

As far as “senseless joins” go : In Fulcro 2 you just implement those by throwing an indent pointer of that name into state. A solution that initial state does for you automatically in many cases. In Fulcro 3 I’m considering letting components act as data roots (via stable idents). In practice doing the explicit “link” in state is so simple that it really isn’t a problem.

👍 4
danielstockton18:04:34

I'm not sure I understand the ident pointer idea. Do you have a simple example?

tony.kay18:04:50

{:table {1 {:join-key [:table 2]} ...}}

tony.kay18:04:46

so that a join on whatever compnent uses table 1 can have [{:join-key …}] just “follow the link”….via db->tree

👍 8
danielstockton08:04:30

In Om.next i've never really been sure how to deal with this.

pvillegas1214:04:58

I’ve been hitting multiple times the following situation: I am updating state in a component that has an input. The input receives a value from fulcro state but does not update. If I place this value in a span next to it, the span updates the value immediately once the mutation fires. however the fulcro input changes every two mutations.

pvillegas1214:04:39

I am changing state from somewhere else in the UI, not directly with the input onChange event

kszabo14:04:15

can you post the code of the input and the mutation?

pvillegas1215:04:19

Why would the span update though?

pvillegas1215:04:34

the span is next to the input, and the span updates its value, not the input

kszabo15:04:38

I can’t tell without the code, it seems odd though 🙂

kszabo15:04:17

If you paste the defsc we can probably help more

pvillegas1215:04:41

(defn ui-component [{:keys [prop prop2]}]
  (println "renders")
  (dom/div
    (dom/span prop)
    (dom/input {:value prop})
    (dom/input {:value prop2 :onChange on-change})))

pvillegas1215:04:00

on-change will mutate such that prop changes, the span shows the different value but the input stays the same @thenonameguy

pvillegas1215:04:56

the print statement also fires, so the functional component does re-render

pvillegas1215:04:01

looks like a bug on the dom/input

kszabo15:04:57

I assume this ui-component is inside a defsc which queries for prop and prop2, right?

kszabo15:04:15

and passes the props down to this fn

kszabo15:04:57

I’m thinking that this might be caused because of an uncontrolled input: http://book.fulcrologic.com/#_controlled_inputs

kszabo15:04:20

that would explain the discrepancy for the state in Fulcro vs in DOM

kszabo15:04:26

but you are passing :value properly

pvillegas1215:04:42

I’m not getting those warnings in the console though

pvillegas1215:04:53

the span shows the right state

pvillegas1215:04:56

the input does not

pvillegas1215:04:14

even the print statement shows the updated state correctly if I include it

👍 4
kszabo15:04:13

my expertise ends here it seems 🙂

pvillegas1215:04:26

Not familiar with how fulcro uses react under the hood

pvillegas1216:04:16

yeah @U3LP7DWPR not sure how that is using the :value prop of the input to update the value in an input

claudiu16:04:54

Havent had any issues with inputs so far. seems a bit strange. Just for testing does it still behave the same if you change it to a defsc ?

pvillegas1216:04:40

My inputs work fine until I need to propagate a change on a value to another input from another

mdhaney17:04:26

FYI, Just changing to defsc on it’s own won’t affect re-rendering. But adding an ident to that defsc component will, so that might be something to try.

tony.kay16:04:19

@pvillegas12 The inputs do a workaround for async updates…you can use raw react inputs by writing a function that is essentially (js/React.createElement "input" (clj->js props))

claudiu16:04:16

Don't the native inputs have a problem with controled props and cursor position ?

tony.kay18:04:41

not if you’re using setState directly…which is what I thought he was doing

tony.kay18:04:51

setState forces a re-render before DOM flush

tony.kay16:04:40

not sure why you’re seeing that…we’ve been using inputs for years now without a reported issue

pvillegas1216:04:36

interesting @tony.kay, before I had something like this

(prim/transact! invoice-component
      [(change-retention
         {:invoice-component invoice-component
          :ui-key            (if (= category specs/ret-ica) :ui/retica :ui/retiva)
          :category          category
          :rate              rate})]))
which changed the value in the change-retention mutation. Changing to
(prim/transact! invoice-component
      [(mut/set-value {:component invoice-component :field field :value value})
       (change-retention
         {:invoice-component invoice-component
          :ui-key            (if (= category specs/ret-ica) :ui/retica :ui/retiva)
          :category          category
          :rate              rate})])))
Fixes it, looks like it renders the inputs twice and forces the refresh

pvillegas1216:04:00

(defmutation change-retention [{:keys [invoice-component category rate]}]
  (action [{:keys [state]}]
    (swap! state
      (fn [s]
        (-> s
          (m/set-value* ...)
          (remove-retention* invoice-component category)
          (add-retention* invoice-component (build-retention category rate))
          )))))

tony.kay18:04:57

@pvillegas12 sorry I don’t have any time next few days to analyze this really at all…other than to say this: The wrapped inputs work by “optimistically” setting their value so that when the async update arrives they are “already correct”. This is a common hack needed in react-based libraries like Fulcro.

claudiu18:04:39

In the fulcro v3 plans remember something about using the local state for fulcro props, whould that remove the need for the workarounds on inputs ?

tony.kay18:04:05

no, there is still an async path due to transact

tony.kay18:04:36

the component local state use would be a “behind the scenes” implementation detail.

tony.kay18:04:05

See this stack overflow explanation for why:

tony.kay18:04:24

React forms are designed completely around component local state, even for controlled inputs….making them “look controlled” from async changes is always a little bit of a hack.

pvillegas1219:04:56

@tony.kay why would having two mutations in the prim/transact call work?

tony.kay19:04:19

I’ve given you all the info about how the things work…I don’t have time to think about it more. ptransact does full-stack one at a time AND it uses setTimeout to defer itself, so you get a delayed and possibly extra addl refresh…perhaps that is why?

pvillegas1219:04:03

ah got it @tony.kay 👍 thanks

exit219:04:50

Is there an example with fulcro forms, where on field change you call a server to fetch updated values and update the fields?

pvillegas1219:04:22

not that I’m aware