Fork me on GitHub
#fulcro
<
2019-02-21
>
danieroux08:02:06

Has anybody wrapped their heads around deploying fulcro using Datomic Ions? Is that even possible?

pvillegas1213:02:44

I already have this setup in production. Deploying fulcro through something like cloudfront and talking to a Datomic Ion as an API through Gateway

Joe Lane15:02:33

Same, I’m using the pedestal-ions library.

mdhaney16:02:53

I’m doing it, too (not quite in production yet). It’s pretty straightforward - just build a Ring handler and call it from the ion function you expose to API Gateway. In dev, you can pass the same ring handler to Jetty for quick local development/testing.

danieroux18:02:27

Thank you, this is good to know and I'll be checking out pedestal-ions for sure.

danieroux18:02:52

Is any kind of websocket connection or server-push possible with Ions?

mdhaney23:02:47

Not yet, but read somewhere (probably here) that they are looking into it.

eoliphant20:02:12

we’re doing Fulcro and Ions oh my as well. No major problems. We’ve been experimenting with something hacky with IoT for websockets

Joe Lane14:02:39

Hey @U380J7PAQ did you know that recently API gateway came out with serverless websocket support? Links: Original post, now slightly outdated: https://aws.amazon.com/blogs/compute/announcing-websocket-apis-in-amazon-api-gateway/ Docs: https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-overview.html AWS Sample SAM project demoing chat with them: https://github.com/aws-samples/simple-websockets-chat-app Mention me if you want to talk more, I don’t look at threads often.

eoliphant15:02:44

Hey @U0CJ19XAM yep, been playing with it a bit, it’s really nice. I think we may just need a bit of extra support in the Ion lib to make it work end to end from Datomic Cloud

thheller09:02:06

@eoogbe I updated the example with full support for live-reload and REPL. forgot that I had a fully working impl for all of this. 😉

10
mdhaney16:02:16

Wait, so you have shadow-cljs working with Expo now, with all the bells and whistles? Oh man, I wish that was the case 3-4 months ago, when I started this project. Good news going forward, at least.

thheller16:02:26

yeah it was kinda working before just had some manual patching involved

thheller16:02:43

I'll be writing some docs about this

thheller16:02:14

there could still be missing things since I don't do real react-native development myself

mdhaney16:02:50

I see you have it working with the latest Expo as well - awesome! I was only able to get the figwheel-bridge based tooling to work up to Expo v30. The tooling changes in RN 56 caused a lot of problems for re-natal and related tooling.

thheller16:02:19

I generated the basics with expo new fulcro-expo

thheller16:02:33

create-react-native-app should work the same

thheller16:02:02

versions pretty much shouldn't matter since this doesn't rely on hacks like re-natal

mdhaney16:02:27

Yeah, this is a much cleaner approach. I’m seriously tempted to switch. Might be worth spending a day to try it out, at least.

thheller16:02:20

no guarantees that this works well but I'd be happy to tweak if something isn't quite right

thheller16:02:39

I only tested the basic hello world right there

mdhaney16:02:41

Hmm, I’ve got a decision to make. If we were to switch, it’s now or never (backend is mostly done, just starting to crank out the UI). I may hit you up if we decide to try it out and need any tweaks. 😉

thheller16:02:23

yeah, happy to help. I've been meaning to do this forever ...

mdhaney16:02:40

Thanks! If I have questions, what’s the best way to reach you? Slack direct message, shadow-cljs channel, this channel, etc.?

thheller16:02:29

either works but DM is probably most reliable

👍 5
Eva O16:02:41

Wow! It works! Thanks!

parrot 5
thheller21:02:23

I'll try to write some proper docs this weekend

pvillegas1214:02:51

I’m returning :fulcro.client.primitives/tempids from a pathom resolver but it is not being picked up by fulcro. I do see it in the response though :thinking_face:

pvillegas1214:02:46

Receiving something like

0: "^ "
1: "~:com.wsscode.pathom/trace"
2: ["^ ", "~:start", 12, "~:path", [], "~:duration", 227, "~:details",…]
3: "^:"
4: ["^ ", "~:fulcro.client.primitives/tempids",…]

pvillegas1215:02:45

Getting {:id #uuid "17ea4e29-41e8-4692-a521-825d87880fcb"} 5990139348123779}

pvillegas1215:02:48

Looks like transit is munging the tempids when sending back to the client

tony.kay15:02:27

You’re using custom server-side code? The transit of Fulcro is augmented to support tempids on client and server (as a type).

pvillegas1215:02:41

Oh snap! facepalm I had to override the transit encoding part because of version issues with Datomic Ions. @tony.kay can you point me in the right direction for tempids to work? I have the following right now:

pvillegas1215:02:57

which is basically hacking at the dependency restriction that exists currently

tony.kay15:02:27

look in transit.cljc in Fulcro

tony.kay15:02:50

fulcro.transit

Robin Jakobsson15:02:32

hello, I’m a newbie at Fulcro (and quite new to ClojureScript as well). What does this error message mean? Invalid join, {:ui/root nil}?

hmaurer16:02:21

@jakobssonrobin can you show me the query you have which involves that join? (`{:ui/root ...}`)

tony.kay16:02:26

A join within a query must have a subquery: {:ui/root [:prop]}. You’re calling prim/get-query to get that subquery, and it is returning nil. I’d guess you didn’t put a query on the component that you’re trying to use in the join

👍 5
hmaurer16:02:27

Hi @tony.kay. Quick general question for you: do you have a rule/philosophy on when to use metadata? I have found myself wondering a few times whether I should put data explicitly on a map, or use metadata

hmaurer16:02:32

(or to anyone else who has an opinion on this 😛 )

tony.kay16:02:36

@hmaurer So there are two places where I generally use metadata: 1. I am adding something on a data structure for internal lib purposes that I don’t want to confuse the user 2. I need to carry data that I don’t want certain subsystems to see/use For example get-query adds :component metadata at each join point, allowing normalization to work….but the component isn’t serializable, so I would not want that to end up visible to transit

tony.kay16:02:59

every symbol (in clj/cljs) gets metadata like file, doc, line etc….that wouldn’t be possible without metadata

tony.kay16:02:37

so I guess that is case 3: You need to add data in a way that is otherwise impossible to carry. This applies for keyed values on a vector

tony.kay16:02:56

(with-meta [:prop] {:component X})

hmaurer16:02:12

Yeah, I think doc, line, etc are very obvious cases of metadata because they are literally “meta” data. It’s data about your program. But in some cases I find myself using metadata to represent “program-level” data (like you with components/queries I guess)

tony.kay16:02:10

I use it often for the hiding aspect: For example I often need a js object in app state (like a js File Object for file upload) but that isn’t serializable, so I don’t want it visible.

hmaurer16:02:36

Makes sense. And do you spec your metadata?

tony.kay16:02:51

so I’ll transact something like this into app state: (swap! state assoc :k (with-meta {} {:js-file file-obj}))

hmaurer16:02:11

oh interesting; that answers another question I had

tony.kay16:02:14

I have been known to spec it, yes

tony.kay16:02:35

that state trick is used enough that I should really add Fulcro helpers for it

hmaurer16:02:50

Okay, follow up question. You say: > 2. I need to carry data that I don’t want certain subsystems to see/use Isn’t this also somewhat answered by ignoring certain keys on a map? (and still passing the whole map along)

hmaurer16:02:11

e.g. you could have a map that has some properties of a user that a function wants to process, and other properties that it doesn’t directly care about

tony.kay16:02:14

yeah, but (2) is exactly what we’re talking abt for state map

tony.kay16:02:30

where you don’t ignore any keys because you might be transmitting the thing over a network

tony.kay16:02:54

so you have to actually hide it better

tony.kay16:02:14

but you’re right: if it is just program data: a namespaced kw is better

hmaurer16:02:14

well you do ignore all keys prefixed by :ui, right? so those wouldn’t need to be serialisable?

hmaurer16:02:17

unless I’m missing something

tony.kay16:02:25

support viewer

hmaurer16:02:02

well so, one thing I have found myself doing is using metadata to store information that I don’t want affecting equality comparison on my data. I am still not sure this is a sensible thing to do, though

tony.kay16:02:05

that js-file would never end up in a query anyhow, because queries don’t come from app state that way

tony.kay16:02:49

I would tend to shy away from that use…unless there is a better reason for using metadata as well

hmaurer16:02:53

for example, in my logic application (that you sort of know about) I have first-order logic formulas, which I often compare for equality. I also have some data about where a formula came from (e..g the name of the rule, etc). Right now I put that data in metadata so as to not affect equality

hmaurer16:02:20

but I could also have used a namespaced keyword, e.g. :formula/source, on the same map as the formula

tony.kay16:02:25

I’d rather see something like (not= (select-keys a ks) (select-keys b ks))

hmaurer16:02:26

and define a function to compare for equality

tony.kay16:02:34

yep, nested keys of things you want to compare is even better because then you get fast ref compares (if they happen to be the same data structure)

hmaurer16:02:57

Makes sense. Thanks for the insights

thheller16:02:43

@tony.kay I did some work testing react-native/expo with fulcro and found a couple things a) fulcro.client.mutations requires cljs.loader but doesn't use it. the goog module loader stuff doesnt' work in react-native so that should probably be removed b) since no npm JS is processed and everything is filled in by metro it starts renaming things because no externs exist. Would be useful for fulcro to typehint the 3 cases where it seems to matter: https://github.com/thheller/fulcro-expo/blob/master/externs/app.txt

thheller16:02:05

should I open an issue about those?

tony.kay16:02:12

Yes please 🙂

tony.kay16:02:19

thanks for looking at it

exit216:02:16

Can anyone advise me on the best way to handle this? Basically I have one data object that represents a form, but the form is more of a multi-step wizard process, so I will be editing pieces of data from the one object throughout different steps: https://gist.github.com/njj/b893eeab4a8656a59e22948608c88cb6

tony.kay16:02:58

just give each step the same ident

5
tony.kay16:02:09

and declare :form-fields differently on each component

exit216:02:32

ok so DetailsForm and AddonsForm would both have the same ident, i.e. (fn [] [:FORM/by-id :booking-form])

tony.kay16:02:38

OR, make one component with all of them that renders differently based on the step

exit216:02:07

so its really just hiding and showing at that point

tony.kay16:02:21

yeah…that would be better, since the add-config will want to affect all fields

tony.kay16:02:28

so I take (1) back…do one component

exit216:02:46

so then I’d have my root, and the form

tony.kay16:02:52

otherwise you’d have to add-config at each step, which makes it difficult to “go back”

exit216:02:53

based on step hide and show different fields

tony.kay16:02:15

yep…put each step in a function or non-stateful component

tony.kay16:02:29

you can still use defsc for the steps…just don’t given them queries/idents

tony.kay16:02:58

and pass the parent component’s props to each of them

exit216:02:14

got it, so more of a presentational component

tony.kay16:02:33

though if you do that, be sure you pass the parent’s this through computed, since some of the form-state functions need that to work right

exit216:02:46

let me try and rework this then

tony.kay16:02:54

functions is probably easiest

exit216:02:12

right now I’m mocking the initial state for the testing, but eventually this shape would come from a fetch

tony.kay16:02:57

So, the other thing to do (back to (1)) is to “virtually split” the data in the data model (e.g. via pathom) so that each step really could be a different ident/form

tony.kay16:02:06

that would give you easier “step” validation.

exit216:02:33

thats kind of the path I was going down, where the data is one source but I somehow break it down for each step

tony.kay16:02:35

because each would have private form state

tony.kay16:02:50

that’s nice for doing things like the logic around “Next”

tony.kay16:02:08

sorry, I’m basically telling you “it can all work”

exit216:02:12

haha thats fine

exit216:02:23

so for example in my gist, I would have separate forms for each step?

tony.kay16:02:09

just realize that add-form-config pays attention to the current components declared fields.

exit216:02:09

I guess whats confusing me is how I would do ::booking (prim/get-query DetailsForm) and ::booking (prim/get-query AddonsForm)

tony.kay16:02:19

you would not

tony.kay16:02:38

so, you want to vary the table name, not the ID. These are all associated with one entity on the server, right?

tony.kay16:02:45

Do you use that thing in the UI as well…e.g. should these form edits immediately show in other places on the UI

tony.kay16:02:50

(so you’d want normalization)

exit216:02:19

I’m not sure if it will yet

exit216:02:22

but thats a possibility

tony.kay16:02:33

I’m trying to determine if you should normalize this

exit216:02:34

I guess there will be a final step

tony.kay16:02:43

I lean towards “yes”

exit216:02:46

so that would be like “look at all this stuff before POSTing”

tony.kay16:02:59

or, show a summary in a sidebar as you go

exit216:02:14

heh I’m not the designer, so you know how that goes 😄

tony.kay16:02:46

yeah. So in general I think I’d probably lean towards making one entity in the db so it is normalized the way you’d use it in the UI in all places.

tony.kay16:02:19

Thus, then thing you have to consider is how form-state works.

tony.kay16:02:15

So, I’m going back to my first suggestion: normalize it, use multiple components, declare form fields in each, and share the ident. When you add-config I believe it won’t overwrite, so that’s a first problem…as you go from step to step you’re going to want to overwrite the form config.

tony.kay16:02:46

The form config is just a copy of the data that is in the form (pristine) and markers for what is “complete”.

pvillegas1216:02:17

@tony.kay I got the write to work

👍 5
pvillegas1216:02:07

but, something is encoding the fulcro tempids somewhere

tony.kay16:02:14

So, at each “step” you’re going to send the props to one of your step components…they will be the same props though. There is a tricky part though. You’ll want to use the “final step with all props” component in the query. The others won’t be in the actual query of the “which page controller”..but since they share an ident and the query will be covered, it’s ok.

pvillegas1216:02:18

I’m printing the result of the defmutation and it looks correct

pvillegas1216:02:34

However, when I get the response to the writer, the tempids are already encoded weirdly

pvillegas1216:02:44

I’m using the lein template, so using the pathom wrappers and all

pvillegas1216:02:41

Basically,

(server/handle-api-request
                         parser
                         ;; this map is `env`. Put other defstate things in this map and they'll be
                         ;; added to the resolver/mutation env.
                         {:ring/request request}
                         (:transit-params request))
is encoding tempids in a weird way

tony.kay16:02:26

@pvillegas12 The client and server in the built-in stuff both use the functions in that same cljc ns

pvillegas1216:02:01

I’m receiving the data correctly

pvillegas1216:02:11

but calling through the parser gives me something like {:id #uuid "a9b8b97a-8a80-4ed3-8320-5013fd3a1cbb"} 5990139348123829,

pvillegas1216:02:50

Basically, the pathom resolver returns a fulcro TempId but my middleware appears to get the above ^

tony.kay17:02:04

so, it’s in your plumbing somewhere…

tony.kay17:02:14

perhaps a pathom plugin

pvillegas1217:02:20

hmmm, unchanged plugins from the lein template.

::p/plugins [(pc/connect-plugin {::pc/register (vec (vals @pathom-registry))})
                                    (p/env-wrap-plugin
                                      (fn [env]
                                        (log/debug "wrap env" env)
                                        (merge env {:config config :conn (get-conn)})))
                                    (preprocess-parser-plugin log-requests)
                                    (p/post-process-parser-plugin p/elide-not-found)
                                    p/request-cache-plugin
                                    p/error-handler-plugin
                                    p/trace-plugin]})
 

tony.kay17:02:16

@njj

(defsc Step1Form [this props]
  {:query [:form/field]
   :form-fields #{:form/field}
   :ident (fn [] (my-form-ident props))}
  ...)

(defsc Step2Form [this props]
  {:query [:form/field2]
   :form-fields #{:form/field2}
   :ident (fn [] (my-form-ident props))}
  ...)

(defsc MasterFormWithAllFields [this props]
  {:query [:form/field :form/field2]
   :form-fields #{:form/field} ; could put all fields here, or none, depending on how you want it to work
   :ident (fn [] (my-form-ident props))}
  ...)

(defsc Controller [this {:keys [ui/step]}]
  {:query [:ui/step {:form (prim/get-query MasterFormWithAllFields)}]
   :ident [:FORM :controller]}
  (case step
    1 (ui-step1-form props)
    2 (ui-step2-form props)
    ...))
If you put all :form-fields on the master, then you can do add-form-config once, but you’ll have to do your “Next” and valid logic based on local field subsets instead of the entire form. If you reset form config at each step you can ask “global” questions about the form and have them only have meaning tied to the step….up to you.

exit217:02:07

wow nice, thanks for the example!

exit217:02:29

maybe a multistep form example on the docs is in our future 😄

tony.kay17:02:19

This is a weird case…often the steps are diff entities and sub-forms, which are covered. Doing multi steps on one entity is valid, but perhaps rarer? Didn’t occur to me when I was writing of course

tony.kay17:02:04

Hm…I’d have to look, but that might actually work better than I thought

tony.kay17:02:47

oh…no, most of the functions work on props, not this…so you don’t even need to declare form fields on the element forms if you use all on master and one call to add-form-config…the declared fields are only really used to build the form config

exit217:02:50

is (my-form-ident props) a function I need to define and have it just return a made up ident?

exit217:02:20

I assume I could just return [:FORM/by-id ::my-form], but how does props play into that? @tony.kay

tony.kay17:02:37

Ideally that ident would be the proper ident for your entity

tony.kay17:02:56

[:booking/id 32]

tony.kay17:02:04

using the id of the entity in the props

tony.kay17:02:15

so then it is normalized over all other uses of the same entity

exit218:02:22

ah I see, so they would all have the same ident

exit219:02:29

I can see the step changing on the mutation, but the main component isn’t seeing that change - any ideas whats wrong?

tony.kay19:02:47

@njj this is a great use for a UI state machine, too 😉

tony.kay19:02:55

let’s see…

tony.kay19:02:54

your ident fn isn’t right

tony.kay19:02:05

is using the wrong ident

tony.kay19:02:37

your initial state is assigning it to :form-proto not :tab

tony.kay19:02:06

The form fields go on the top-level form, not the steps

exit219:02:20

hmm, I thought I tried that [:form-proto :ui/step]

tony.kay19:02:38

just a min…

tony.kay19:02:03

@njj You’ve put the form in the wrong spot…

exit219:02:01

I guess I’m confused on where it would go then based on your example, I must have missed something

tony.kay19:02:30

you’re overlaying the form…you need a “main one” that acts like the whole form, and then the steps just render “in place” of it

tony.kay19:02:47

that other component isn’t any more than layout, and isn’t part of the forms

tony.kay19:02:51

you can move some of that functionality into the form if you want (the next control) or leave it separate and send computed step

exit219:02:57

cool let me check it out

tony.kay19:02:08

untested…I just hand-edited your gist

exit219:02:57

right of course 🙂

tony.kay19:02:46

add-form-config needs the BookingForm class as an arg

👍 5
pvillegas1220:02:25

I’m facing some issues with creating forms with nested relationships. If you have a parent model which has a Datomic ref to many child models, I’m currently solving the problem by having the parent model have all the form-fields for the parent model, and nest the form that way. It seems that calling dirty-fields gets rid of any subforms which do not have a tempid. If you had a ref to another model that already exists, how can you add it to the payload you are sending to the backend?

tony.kay20:02:50

@pvillegas12 so, nested forms should “work” in terms of delta etc. There are a few possibilities: 1. You’re not nesting the subforms correctly 2. You’re not initializing the form config correctly 3. There is a bug in form-state with nested forms 4. I guess there are other possibilities…

tony.kay20:02:07

the tempid check should give you all fields

tony.kay20:02:21

otherwise (without a tempid) you’ll only get the ones that have changed

tony.kay20:02:44

if you’re not getting the changes, but you are getting them all on tempid, then it could be a bug

tony.kay20:02:18

I do not personally use nested forms, and I don’t know how much they;ve been used by the community

tony.kay20:02:42

so the chances it is bug free (I’ve never seen a bug report) on my first attempt at making it are slim 🙂

pvillegas1220:02:46

So, how do you make a form that adds let’s say an invoice to a company?

tony.kay20:02:01

When you add the new thing, you give it a tempid

pvillegas1220:02:10

right, how do you persist the relationship?

pvillegas1220:02:29

in datomic terms, the :company/invoices or invoice/company ref

tony.kay20:02:41

so, that is already a to-many relation on your UI

tony.kay20:02:51

so you joined it there

pvillegas1220:02:03

that’s what I am doing right now

tony.kay20:02:07

I guess I don’t understand your q

pvillegas1220:02:27

Let’s say I have the invoice/company ref, and I want to add that data on my form submission

pvillegas1220:02:57

I am doing the join in the UI, but when I do the dirty-fields call, the invoice/company ref is dropped, as it does not have a tempid

pvillegas1221:02:07

I guess I could merge it manually and send the payload over

tony.kay21:02:20

so, do you understand how dirty fields works?

pvillegas1221:02:20

What I don’t want to do is to have to repeat the form-fields for a company all over the place where I really want forms for models that refer to that company

pvillegas1221:02:47

It does a diff between new and old data at the attribute level

pvillegas1221:02:53

(and traverses subforms it seems)

pvillegas1221:02:28

If you want to add a relationship, do you manually merge it with the data you are persisting?

tony.kay21:02:08

no, you include the subform join key in the :form-fields

pvillegas1221:02:40

when I do that join, when I give it the component class, does that class have to implement the form-fields?

tony.kay21:02:01

the process is recursive and composeable

tony.kay21:02:26

so, if there is a “child form” it has to declare the fields it has

pvillegas1221:02:32

If I want to add a child model to a has many relationship, I have to define two forms: One for the parent and then one for the child with the join in it?

tony.kay21:02:14

the code example there is exactly that

tony.kay21:02:55

and if you look in the console log, it shows what the server gets

tony.kay21:02:25

adding a phone number:

{:id 21,
 :diff
 {[:person/by-id 21]
  {:book.forms.form-state-demo-2/phone-numbers
   [[:phone/by-id 1]
    [:phone/by-id 2]
    [:phone/by-id
     #fulcro/tempid["b033085d-077c-4cf4-932a-9a6ec3f5a39a"]]]},
  [:phone/by-id #fulcro/tempid["b033085d-077c-4cf4-932a-9a6ec3f5a39a"]]
  {:book.forms.form-state-demo-2/phone-type :home,
   :book.forms.form-state-demo-2/phone-number ""}}}

tony.kay21:02:45

the person form is sent with the relation (as an ident), and it includes the new phone number

tony.kay21:02:48

with attributes

tony.kay21:02:11

so you convert the new “edge” from an ident to the proper thing in Datomic