Fork me on GitHub
#fulcro
<
2019-01-23
>
mss15:01:27

unrelated question to the above but still trying to clarify how validation in a form works. I have form fields declared in my component, the same form fields are reflected in the query/initial state. my component has an ident as well. for whatever reason, calling (fs/get-spec-validity props) returns :valid despite none of the fields being marked as complete.

mss15:01:35

any idea why that could be happening?

tony.kay15:01:01

@mss I use the form state stuff regularly. It works. Are you remembering to add form config?

tony.kay15:01:26

Are you following the book, or just doc strings? There are tons of examples with full source in the book.

mss16:01:22

I’m following the book. I was under the impression I was doing it correctly but clearly something is off. have the form-config-join added to my query

mss16:01:20

per the examples and book if something isn’t complete, it should be :unchecked. somehow getting a :valid result for my component’s props despite not calling mark-complete

tony.kay16:01:48

the answer to my question: have you added the form config to your instance?

tony.kay16:01:40

Looking in inspect, do you have a valid ident pointing from the instance’s form join to a real form config in state?

tony.kay16:01:13

the functions cannot work properly if the config isn’t present. Could use better console error messages around that, if you’re not getting any

mss16:01:40

unfortunately working in an electron app and haven’t gotten the inspector to work with it

mss16:01:06

by “adding the form config to your instance” you mean :query [fs/form-config-join]

mss16:01:10

correct?

tony.kay16:01:27

add-form-config and add-form-config*

tony.kay16:01:07

which MUST be run to initialized the form config on any instance that acts as a form (via a mutation, e.g.)

tony.kay16:01:10

one is usable in initial state, the other inside of mutations

mss16:01:40

ah I was under the impression that add-form-config was only used when you wanted to integrate a newly generated ident whether on the client/server

tony.kay16:01:48

nope, it records the “pristine” state, marks things as incomplete, etc.

tony.kay16:01:54

that “state” has to go somewhere 🙂

mss16:01:39

gotcha, that def makes sense. so a call in the containing component would look like: :my-form-data (fp/get-initial-state (fs/add-form-config MyForm [:forms/by-id :my-form) {})?

mss16:01:53

wrapping the component before initial state is pulled?

tony.kay16:01:03

no get-initial-state, and you’ll have to use the lambda form of initial state

tony.kay16:01:37

I always do it in mutations…but I think that function returns the tree for the component with the tree added…so it would be:

(defsc X [_ _]
  {:initial-state (fn [params] (add-form-config X [:x/by-id 22] {:id 22 :x/name "boo" ...})

tony.kay16:01:49

I’ve not build an electron app…is there any way to just build stuff in workspaces, and then combine the bits into the electron app? That would make life a lot better.

tony.kay16:01:05

I mean, it isn’t native

tony.kay16:01:14

and I build all my web stuff that way

mss16:01:27

ehh for bits and pieces yes but for certain things it’s basically impossible to work on sans an electron app. the fulcro-inspect has some code related to remote inspection and a client side script to start a server and broadcast changes. when I get some time I should take a crack at extending that and getting it working, would sidestep the whole issue

tony.kay16:01:09

what pieces are electron-specific???

tony.kay16:01:24

I mean: forms are pretty generic

tony.kay16:01:39

(and I agree, Electron support in Inspect would be nice)

mss16:01:43

sandboxed webviews for third party content. the implementation is a little different on electron vs just an iframe

mss16:01:12

I should just pull these other bits out and work on them in an actual browser, you’re absolutely right 😝

hmaurer17:01:59

@tony.kay Hello 🙂 I have a very specific question on error handling with Fulcro. I am trying to catch API requests that respond with 401 and, when it happens, redirect to some route. I read through the Fulcro book and it seems like the simplest way to do this would be to set a :network-error-callback. I did so, but it doesn’t seem to be triggered. Here’s my code:

(defn error-handler [state status-code error]
  (log/warn "Global callback:" error " with status code: " status-code))

(defn ^:export init []
  (reset! SPA (fc/new-fulcro-client
               :network-error-callback error-handler
...
and the console output: https://puu.sh/CBatW/67a402b2e1.png

hmaurer17:01:14

Any idea why this could be? I cleared the shadow cache

tony.kay17:01:26

@hmaurer not off the top of my head. The networking is involved (e.g. the remote you’ve configured)…if you used custom networking I think that might be able to break it.

hmaurer17:01:01

Ah. I didn’t make any changes to the networking from the lein template:

(defn ^:export init []
  (reset! SPA (fc/new-fulcro-client
               :network-error-callback error-handler
               :started-callback (fn [elogic-beyond]
                                   (df/load elogic-beyond :all-users root/User))
                ;; This ensures your client can talk to a CSRF-protected server.
                ;; See middleware.clj to see how the token is embedded into the HTML
               :networking {:remote (net/fulcro-http-remote
                                     {:url                "/api"
                                      :request-middleware secured-request-middleware})}))
  (start))

tony.kay17:01:07

says right in the doc string “Only works if you choose to use the default built-in networking (ignored if you also specify :networking).”

tony.kay17:01:32

so, sorry…I didn’t think of that abt template

hmaurer17:01:38

Ah, I see, sorry! I thought I was using the built-in networking and missed the “ignored if you specify :networking”

hmaurer17:01:51

that explains it

tony.kay17:01:05

So, you could just add to the middleware

hmaurer17:01:26

I just tried the middleware approach; it works perfectly except for one thing: is there a way to write an async middleware? i.e. I would like to trigger the redirect but return the equivalent of a promise that doesn’t resolve, so Fulcro “hangs” (as if the request was still in flight) until the redirect completes

tony.kay18:01:06

what you’re saying doesn’t make sense in the context of Fulcro. The rendering/interaction is purely a function of what you’ve rendered. The network layer is about moving data from the network INTO that state…so, what you want to do is end up with the correct state to show what you want…“hang” isn’t a thing.

tony.kay18:01:43

You can change the tx and body of the response (where tx is the query used for merge)…THUS middleware can cause any change to app state imaginable

tony.kay18:01:56

If you want to make a “pessimistic mutation” (e.g. cause a tx that is processed in steps) then that is a higher level abstraction, not a network one, in Fulcro

tony.kay18:01:02

ptransact! e.g.

tony.kay18:01:20

I highly suggest you look at incubator’s newer pessimistic transactions.

tony.kay18:01:59

but then you haven’t actually described the feature….I’d assumed what you wanted was something that would cause a UI error on network errors in a “general way”…you can handle that in a bunch of ways: 1. Save the reconciler in a global place, and add a network layer hook that transacts against it (in a deffered (setTimeout) way) 2. Do middleware transform of tx and body to cause a merge of state with the app that will show a blocking dialog 3. Write you own custom network layer…heck, you could even call js/alert in it if you want 4. A custom networking object can indicate that is supports progressive updates as well, which means it can cause a merge (via the update callback) at any time to change the UI…it’s how file upload progress works in file upload remotes.

hmaurer18:01:50

@tony.kay thanks for the detailed reply. This isn’t quite what I meant, though. What I was wondering is whether there was a way, through a middleware (or similar), to tell Fulcro that the request was still in flight. An async middleware, basically. Perhaps i am thinking about this wrong though, I’ll look into what you mentioned.

tony.kay18:01:36

you can use (4) for that

tony.kay18:01:12

it isn’t async middleware…it is async behavior you implement at the network layer

tony.kay18:01:24

but interaction with Fulcro is always a “merge of data”

tony.kay18:01:55

but recognize that “in flight” just means “submitted, no response”

tony.kay19:01:25

you can implement that by setting a flag, running a pess. tx, and having the flag be unset after a step completes

tony.kay19:01:51

which can be viewed as a distinct series of app state updates (merges or mutations)

hmaurer19:01:22

@tony.kay I see, but doesn’t my use-case make sense? Here’s the middleware I wrote (really dumb):

(defn auth-response-middleware [response]
  (if (= 401 (:status-code response))
    (redirect! "/sign-in"))
  (net/wrap-fulcro-response response))
The issue I was trying to get around is that the redirect (through window set location) takes some time, and in that time Fulcro is going to process the response and the UI might flicker a little bit

tony.kay19:01:00

well, depending on what you mean by redirect!

hmaurer19:01:09

(defn redirect! [loc]
  (set! (.-location js/window) loc))

tony.kay19:01:36

ah…you’re trying to abandon the app altogether?

tony.kay19:01:09

so you’re asking me “is there a way to prevent the processing of the response” I think

hmaurer19:01:15

@tony.kay yes. I found it easier to implement my auth flow outside of Fulcro, so I can just put all API requests behind an auth check (instead of trying to let some API requests, through, i.e. auth requests)

tony.kay19:01:31

sure, modify tx/body in the response to be nil

tony.kay19:01:39

nothing to merge, no ui update

tony.kay19:01:18

“nil” might need to be tx as [] and body as {}

tony.kay19:01:57

basically Fulcro trusts the middleware to tell it “what was asked for, and what is the response”…if you say nothing and nothing, then…nothing

hmaurer19:01:04

@tony.kay ah perfect, thank you. I should have formulated my question better; that’s what I was looking for

tony.kay19:01:26

yeah: (assoc response :transaction [] :body {})

tony.kay19:01:31

as the return of the mw

tony.kay19:01:44

no async madness req’d 🙂

hmaurer19:01:21

@tony.kay like this? (assoc ((net/wrap-fulcro-response) response) :transaction [] :body {})). Or straight up on the response?

tony.kay19:01:13

so, the base middleware decodes the resp on 200…so not needed in the error case

tony.kay19:01:09

technically, on non-200, it returns what we want…so you you original should have worked 😕

tony.kay17:01:11

and put your hook there

tony.kay17:01:28

the networking layer is the only place that code is visible

hmaurer17:01:48

💯, thank you

hmaurer19:01:10

@tony.kay unrelated but while working with the new lein template I stumbled upon a bug with Hiccup 1.0.5 when you use dot classes in conjunction with dynamic classes, i.e. [:div.foo {:class (if error "has-error)}]. I asked weavejester and the bug has been fixed in Hiccup 2 alpha 2 (although I found another bug in that version for which I have yet to file an issue). Would you accept a PR on the lein template to upgrade to Hiccup 2 alpha 2?

hmaurer19:01:27

or would you rather keep it on a non-alpha version?

tony.kay19:01:41

I think hold off until a release, thanks

hmaurer22:01:52

Quick q: what is the syntax/approach with Fulcro to “combine” queries of components? i.e. if a component renders two sub-components that each need to display “person” data, can I compose the queries of the two sub-components into the parent component?

hmaurer22:01:15

And likewise, when using df/load, can I merge the queries of more than one component?

hmaurer22:01:24

(if that makes sense)

tony.kay23:01:11

short answer is “no”

tony.kay23:01:29

normalization requires that queries by properly annotated with component metadata, which get-query does

tony.kay23:01:46

so the long answer is “maybe”

tony.kay23:01:46

but in general you just issue multiple loads…they get combined into one network request if they are submitted together

tony.kay23:01:15

and yes, you can of course compose them into a parent (under unique keys)…not sure what you mean there @hmaurer

hmaurer23:01:54

“compose” was poor wording. What I meant was basically merge. What if the parent wants to display two child components each requiring different “person” data from the spouse?

hmaurer23:01:11

the naive approach would be using into, which won’t work. Is there a non-naive approach?

hmaurer23:01:29

(I am basically looking for an equivalent to graphql fragments. You have two sub-components that each want some fields from a “user”, and the parent component has to specify that it requires the union of the fields of all its sub-components)

hmaurer23:01:24

(solved by “virtual edges”)

tony.kay23:01:01

(defsc Parent [this props]
  {:query [{:spouse (prim/get-query Person) :children (prim/get-query Person)}]}

tony.kay23:01:06

BUT this is definitely *NOT*:

(defsc BadParent [this props]
  ;; NEVER do this
  {:query (fn [] (into (prim/get-query A) (prim/get-query B)))})

hmaurer23:01:23

yep, I think Pathom’s author mentioned metadata being associated to queries in his conj 2018 talk… which is why I was wondering how it could be done (since simply using into wouldn’t work)

hmaurer23:01:33

ah, I see, ok

tony.kay23:01:02

each get-query returns an annotated query

tony.kay23:01:08

which is how normalization works

tony.kay23:01:41

thus you cannot “steal or borrow” a query either

tony.kay23:01:02

(defsc X [_ _]
  {:query (fn [] (prim/get-query Y))} ;; BAD idea

tony.kay23:01:47

but this is fine:

(def shared-props [:x :y])

(defsc A [_ _]
  {:query (fn [] (into shared-props [:z]))})

(defsc B [_ _]
  {:query (fn [] (into shared-props [:b]))})

tony.kay23:01:32

which is very different from before, because the earlier one was losing the distinction (and normalization info) between A/B as normalized components

tony.kay23:01:15

so another one that is perfectly fine:

tony.kay23:01:55

(defsc X [_ _]
  {:query (fn [] (into [:a :b] (zipmap [:spouse :children :best-friend :work-buddies] (repeat (prim/get-query Person)))))})

tony.kay23:01:03

(assuming I didn’t make a typo)

tony.kay23:01:38

this is fine because it’s just another way of writing a legal query: [:a :b {:spouse (prim/get-query Person)} ...]

tony.kay23:01:59

FYI, get-query essentially returns (with-meta [:x] {:component X}) for a component X with query [:x]…nothing that special

tony.kay23:01:17

it’s just recursive

hmaurer23:01:23

Oh, that’s good to know. Also I was being silly, I think I just realised what I am asking might be the :>foo feature Pathom’s author talked about at conj. I am not sure what the name is

tony.kay23:01:31

well, you write it that way, with your composition 🙂

tony.kay23:01:47

placeholder is what Wilker calls them

hmaurer23:01:48

but if I recall correctly it stays within the same resolution “context” but allows nesting, so you can fetch data for multiple components

hmaurer23:01:56

ah yes, placeholders

tony.kay23:01:02

right…you can “spread” person across the graph

tony.kay23:01:11

very cool feature

tony.kay23:01:33

not sure what term I prefer…“virtual edge” “self-reference”…

hmaurer23:01:40

so am I correct in thinking that’s basically the solution to the “merging” issue? You can’t “merge” queries of child components because of the issues you described above, but you can use placeholders to achieve a similar result

tony.kay23:01:41

but “placeholder” is good

hmaurer23:01:30

“virtual edge” is a nice one

tony.kay23:01:35

yep, and pathom just makes the magic happen

tony.kay23:01:02

[:person/name {:>address [:person/street :person/city]}]

tony.kay23:01:13

“invent-a-join” 😄

tony.kay23:01:37

where the outer query would be on Person, and the inner on Address

hmaurer23:01:03

yup, neat 🙂

tony.kay23:01:45

Channel: So, I’ve been thinking that once a week I might be able to do something like a “group Q/A” via some kind of screen sharing service (YouTube? Twitch? Not sure what option would be best). This would be a paid attendance sort of thing, but the ideal would be for it to be like a coding dojo where we could share code/tips/etc. on Fulcro. Any interest?

👍 35
hmaurer23:01:02

It depends on the format / content / cost, but overall I would be interested and would definitely attend the first one if it’s at a reasonable GMT hour 🙂

hmaurer23:01:35

Also I would be particularly interested if there is a focus on architecture / design.

piobaire23:01:34

Twitch recently made changes that would make this a lot easier. Not necessarily advocating for it, just saying that they added a lot of new categories, and streaming to twitch is an absolute breeze. It’s also far more interactive than YouTube. I would be happy to help you both get that set up, and stream it.

👍 5
tony.kay23:01:53

I was thinking in the $10-20/person range. Not sure the format though…I’d have to give it more thought. Might be a good way to motivate me to build some more demo apps that we can share, but in collaboration with you all so I’m hitting “sore spots”

tony.kay23:01:56

Does twitch do shared audio with the group, or just one-way. I’m guessing participant count would be quite low, so audio for everyone would be best

hmaurer23:01:30

Google Hangout seems like a good option if audio for everyone is desirable?

👍 5
tony.kay23:01:31

also, being able to switch screen shares amongst participants

hmaurer23:01:43

It supports screen share and audio

tony.kay23:01:04

zoom is also an option I guess

tony.kay23:01:12

supports lots more ppl I think, if needed

piobaire23:01:22

If you wanted multiple participants, you can use something like discord or any other voice app. Then use that as an audio source for OBS.

tony.kay23:01:52

yeah, but I think that is still one-way on the screen share

piobaire23:01:11

Correct - it’s one way only.

hmaurer23:01:29

Twitch for the first one, where you build a Fulcro application with WebRTC, that we then use in the following weeks? 😄

piobaire23:01:47

that’s a great idea honestly! ❤️

tony.kay23:01:10

I need that, actually, for one of my projects… 😉

piobaire23:01:45

That is true!

Daniel Hines00:01:42

This is a great idea. I’d also recommend recording the sessions since your users are spread across so many time zones.

Daniel Hines00:01:02

Something like these sessions would be perfect since I don’t yet use Fulcro professionally and can’t devote nearly the time I want towards learning/working with it. Plus, anything the community can do to keeping you working on the library is a big plus!

hmaurer22:01:51

@tony.kay do you have a date for a first one of these? or is it still at the idea stage?

tony.kay22:01:56

looking at level of interest…doesn’t make too much sense if there are not at least 5-10 ppl interested

tony.kay22:01:27

10 @ $20 ea would start to make it worth the time/effort

tony.kay22:01:50

below that and I have much more lucrative things to do with my time.

Daniel Hines22:01:17

What level of instruction are you thinking? Deep dive into advanced features, or building an understanding from the ground up (wondering if I should invite non-Fulcro users whom I hope to convert). Both are great.

tony.kay22:01:30

I’m game, based on interest, to narrow the topic per session to whatever there is interest in. “How do I do X?” is what I was thinking…perhaps participants submit in advance, and each gets a 10-minute dive (or n-times that if there are n interested parties interested in it). I’m open to format.

wistb19:01:58

I am interested in a structured beginner level training,