This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2019-02-21
Channels
- # announcements (6)
- # bangalore-clj (1)
- # beginners (46)
- # cider (21)
- # cljs-dev (30)
- # cljsjs (3)
- # clojure (131)
- # clojure-dev (20)
- # clojure-europe (2)
- # clojure-italy (12)
- # clojure-nl (11)
- # clojure-russia (4)
- # clojure-spec (55)
- # clojure-uk (57)
- # clojurebridge (1)
- # clojured (1)
- # clojurescript (55)
- # cursive (11)
- # data-science (1)
- # datomic (23)
- # duct (1)
- # emacs (1)
- # events (1)
- # figwheel-main (2)
- # fulcro (219)
- # graphql (16)
- # immutant (1)
- # jackdaw (3)
- # java (6)
- # juxt (30)
- # kaocha (8)
- # mount (3)
- # nyc (1)
- # off-topic (16)
- # pathom (48)
- # pedestal (1)
- # re-frame (71)
- # reagent (17)
- # ring-swagger (3)
- # shadow-cljs (96)
- # spacemacs (21)
- # specter (8)
- # speculative (20)
- # sql (21)
- # test-check (2)
- # tools-deps (12)
- # vim (6)
Has anybody wrapped their heads around deploying fulcro using Datomic Ions? Is that even possible?
I already have this setup in production. Deploying fulcro through something like cloudfront and talking to a Datomic Ion as an API through Gateway
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.
we’re doing Fulcro and Ions oh my as well. No major problems. We’ve been experimenting with something hacky with IoT for websockets
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.
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
@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. 😉
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.
there could still be missing things since I don't do real react-native development myself
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.
versions pretty much shouldn't matter since this doesn't rely on hacks like re-natal
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.
no guarantees that this works well but I'd be happy to tweak if something isn't quite right
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. 😉
Thanks! If I have questions, what’s the best way to reach you? Slack direct message, shadow-cljs channel, this channel, etc.?
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:
Receiving something like
0: "^ "
1: "~:com.wsscode.pathom/trace"
2: ["^ ", "~:start", 12, "~:path", [], "~:duration", 227, "~:details",…]
3: "^:"
4: ["^ ", "~:fulcro.client.primitives/tempids",…]
Getting {:id #uuid "17ea4e29-41e8-4692-a521-825d87880fcb"} 5990139348123779}
Looks like transit is munging the tempids when sending back to the client
You’re using custom server-side code? The transit of Fulcro is augmented to support tempids on client and server (as a type).
Oh snap! 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:
which is basically hacking at the dependency restriction that exists currently
thanks!
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}
?
@jakobssonrobin can you show me the query you have which involves that join? (`{:ui/root ...}`)
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
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
@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
every symbol (in clj/cljs) gets metadata like file, doc, line etc….that wouldn’t be possible without metadata
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
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)
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.
so I’ll transact something like this into app state: (swap! state assoc :k (with-meta {} {:js-file file-obj}))
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)
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
where you don’t ignore any keys because you might be transmitting the thing over a network
well you do ignore all keys prefixed by :ui
, right? so those wouldn’t need to be serialisable?
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
that js-file would never end up in a query anyhow, because queries don’t come from app state that way
I would tend to shy away from that use…unless there is a better reason for using metadata as well
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
but I could also have used a namespaced keyword, e.g. :formula/source
, on the same map as the formula
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)
@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
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
ok so DetailsForm and AddonsForm would both have the same ident, i.e. (fn [] [:FORM/by-id :booking-form])
otherwise you’d have to add-config
at each step, which makes it difficult to “go back”
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
right now I’m mocking the initial state for the testing, but eventually this shape would come from a fetch
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
thats kind of the path I was going down, where the data is one source but I somehow break it down for each step
just realize that add-form-config
pays attention to the current components declared fields.
I guess whats confusing me is how I would do ::booking (prim/get-query DetailsForm)
and ::booking (prim/get-query AddonsForm)
so, you want to vary the table name, not the ID. These are all associated with one entity on the server, right?
Do you use that thing in the UI as well…e.g. should these form edits immediately show in other places on the UI
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.
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.
The form config is just a copy of the data that is in the form (pristine) and markers for what is “complete”.
but, something is encoding the fulcro tempids somewhere
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.
I’m printing the result of the defmutation
and it looks correct
However, when I get the response to the writer, the tempids are already encoded weirdly
I’m using the lein template, so using the pathom wrappers and all
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@pvillegas12 The client and server in the built-in stuff both use the functions in that same cljc ns
I’m receiving the data correctly
but calling through the parser gives me something like {:id #uuid "a9b8b97a-8a80-4ed3-8320-5013fd3a1cbb"} 5990139348123829,
Basically, the pathom resolver returns a fulcro TempId
but my middleware appears to get the above ^
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]})
(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.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
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
is (my-form-ident props)
a function I need to define and have it just return a made up ident?
I assume I could just return [:FORM/by-id ::my-form]
, but how does props play into that? @tony.kay
@tony.kay trying out your steps ideas now, https://gist.github.com/njj/b893eeab4a8656a59e22948608c88cb6#file-form-cljs-L46-L48
I can see the step changing on the mutation, but the main component isn’t seeing that change - any ideas whats wrong?
I guess I’m confused on where it would go then based on your example, I must have missed something
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
you can move some of that functionality into the form if you want (the next control) or leave it separate and send computed step
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?
@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…
if you’re not getting the changes, but you are getting them all on tempid, then it could be a bug
I do not personally use nested forms, and I don’t know how much they;ve been used by the community
interesting
so the chances it is bug free (I’ve never seen a bug report) on my first attempt at making it are slim 🙂
So, how do you make a form that adds let’s say an invoice to a company?
right, how do you persist the relationship?
in datomic terms, the :company/invoices
or invoice/company
ref
right
that’s what I am doing right now
Let’s say I have the invoice/company
ref, and I want to add that data on my form submission
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
I guess I could merge
it manually and send the payload over
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
It does a diff between new and old data at the attribute level
(and traverses subforms it seems)
If you want to add a relationship, do you manually merge
it with the data you are persisting?
when I do that join, when I give it the component class, does that class have to implement the form-fields
?
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?
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 ""}}}