Fork me on GitHub
#fulcro
<
2017-12-14
>
wilkerlucio00:12:05

@adamvh be aware that local state is evil, you rarely have a good reason to use it, and later you probably regret... any reason to don't use the app state instead?

adamvh00:12:52

well, i've since re-worked it to use the app state, but, my thinking runs this way:

adamvh00:12:13

aren't components with local state a little bit more re-usable?

adamvh00:12:25

since they don't have to know anything about the app state?

wilkerlucio00:12:47

if you give idents to your components, they always know where their own state is, which makes then highly re-usable

adamvh00:12:56

i'm trying to implement a datepicker as a fulcro defsc

adamvh00:12:25

and there's a bunch of stuff a datepicker wants to know (e.g. what month the user is looking at right now)

adamvh00:12:35

that seems like it would kind of be polluting the app state

wilkerlucio00:12:00

put everything there, it's the best approach, this way you get the whole trackability story

adamvh00:12:10

so the pattern for a reusable datepicker would be something like

adamvh00:12:33

have :datepickers/by-id as a top-level thing in the app state

adamvh00:12:24

and then those would have ids

adamvh00:12:44

and maybe they would get mutations from computed

adamvh00:12:53

to execute when the user selects a date

wilkerlucio00:12:19

you can make the whole management inside of the data picker component

wilkerlucio00:12:29

and have the parent provide a callback to update the value

wilkerlucio00:12:38

like a text field, or any input for that matter

wilkerlucio00:12:26

I gotta leave now, but I suggest you check the sources on fulcro inspect: https://github.com/fulcrologic/fulcro-inspect/tree/develop/src/fulcro/inspect/ui

wilkerlucio00:12:48

there are lots of cool patterns on how to write and manage the components there

adamvh00:12:09

thanks for your help!

wilkerlucio00:12:26

no problem šŸ™‚

LL02:12:12

@tony.kay Thanks, but apologize, I cannot understand what you said well, the easy-server's make-fulcro-server made itself a build-in-component "handler", Is this "handler" just for web server? In the websockets demo, there is a custom "handler" which is for websockets, and there will have two "handler" components, if I put the websockets' handler in :components in the make-fulcro-server.

LL03:12:05

@tony.kay I just read the https://github.com/fulcrologic/fulcro/blob/3ca91cf7b0d98e2c863dd0d19da43cbdaa132463/README-websockets.md

(require '[fulcro.websockets.components.channel-server :as cs])

(defn make-system []
  (core/make-fulcro-server
    :config-path "config/app.edn"
    :parser (prim/parser {:read logging-query :mutate logging-mutate})
    :parser-injections #{}
    :components {:channel-server  (cs/make-channel-server)
                 :channel-wrapper (make-channel-listener)}
    :extra-routes {:routes   ["" {["/chsk"] :web-socket}]
                   :handlers {:web-socket cs/route-handlers}}))

LL03:12:08

There is not a custom handler, instead, use :extra-routes.

LL06:12:20

@tony.kay I read the comments of the simple-channel-server, and I want to use defmutation, defquery-root. So I decide use the method in websockets demo to create system-map, and use :dependencies of the simple-channel-server to add db component in the parsing environment. It seems work. But I'm not sure if doing this ok?

tony.kay13:12:13

That's exactly right @tpliliang

adamvh14:12:35

@tony.kay what is the :ref key in the env map for mutations? I don't see it mentioned in the dev guide anywhere, but I see it used in the source.

claudiu14:12:14

@adamvh the component ident.

adamvh14:12:04

specifically the return value of ident called on the first argument of the call to transact! that initiated the mutation?

claudiu15:12:50

yep šŸ™‚

adamvh15:12:22

and this is useful because in a mutation i can (assoc-in @state (conj ref :abitrary-key) new-value) to change :arbitrary-key in my component's props

adamvh15:12:53

got it, thanks

cjmurphy15:12:55

But what happens when you call the same mutation but without specifying anything for ref?

cjmurphy15:12:46

Oh right I see - exactly what you said @adamvh

cjmurphy15:12:37

So when you call transact! you can specifically set it (the ref is one of the arguments to transact!), or if you are calling transact! from a component and have not put in anything for the ref parameter then basically (ident this) will be used.

cjmurphy15:12:45

I guess when you call transact! passing in the reconciler rather than this is when you need to be explicit about what ref is.

cjmurphy15:12:55

Wonder what happens if you call transact! with a reconciler as first argument but without putting anything for the ref parameter??

thheller15:12:33

am I doing something wrong when Iā€™m thinking about introducing a component just to get a new ident?

cjmurphy16:12:25

I would say not at all @thheller

adamvh16:12:52

@cjmurphy and you can code your mutations to be defensive about the possibility of a nil ref, e.g. (conj (into [] ref) :keyword)

adamvh16:12:49

where you'd just assume that the top-level atom is your component's props

cjmurphy17:12:11

Got it - where keyword is a link (not atom). That makes sense - there's no ref, so the mutation (in this instance of the transact! call) has no relationship with a real (having an ident) component, so the join must be a link (i.e. not in a table). Is that way of doing it used in Fulcro Inspect?

wilkerlucio17:12:32

@cjmurphy I always expect ref on my transactions, I used to use transactions that don't, but I learned that when you do that, you can't add multiple instances of that same component, making your component more rigid, so these days I add idents to everything that has state (except the root, which is usually a very thin component that just points the real root, which has an ident), this approach makes the components flexible to re-use

cjmurphy17:12:30

Yes I'm the same. I would go so far as to say I don't even like links! Even one-off configuration stuff I would put in a table with a suitable 'configuration' name. I'm more than happy to have plenty of tables with only one record (that the way I think of it) - like in your Fulcro Inspect source code where you use "main" as the id part of an ident.

adamvh17:12:47

and from perusing the inspect source code, the pattern for re-usable components seems to be to put them in a namespace and shove a table of them into the app state under the key :namespace/id

adamvh17:12:23

unlike in all the dev guide stuff where your component's props has a key :db/id and the app state has a thing :some-keyords/by-id

cjmurphy17:12:56

Yes it is interesting to see the mutations in the same ns as the components, and the use of :: - which means they have to be in the same ns.

adamvh17:12:18

i.e. all the defsc are in their own namespace and have {:ident [::id ::id]}

adamvh17:12:47

which seems like a good pattern to me if you want to make your component into a library

cjmurphy17:12:30

Personally I really like the convention of using :table-name/by-id. Harder for me to read without that convention.

adamvh17:12:40

yeah, but the key is that if table-name is the same as the namespace where your defsc lives, then it isn't going to collide with some random top-level key that client code happens to have put into app state

cjmurphy17:12:43

But yes having the :: means you can't have :table-name/by-id.

adamvh17:12:01

well you can ::by-id

adamvh17:12:14

and the only thing that really changes is you can't pluralize your namespace

cjmurphy17:12:15

Oh yes, okay.

cjmurphy17:12:44

But do you realise that there are many clients - as in many Fulcro clients?

cjmurphy17:12:11

Each tool will have its own reconciler, and its own root.

cjmurphy17:12:41

That will be a separate Root from the host/target.

cjmurphy17:12:04

So I don't see any problems with collisions.

adamvh17:12:47

you also give users an indication where this table in their app-state came from, more so than just picking a key

cjmurphy17:12:09

So I don't yet see a great need for using ::, unless you can't think of a name for your table - so you want your table name to be the same name as the ns you are in.

adamvh17:12:01

i think if you're trying to offer your component as a library it makes sense

adamvh17:12:18

that people who require the namespace your library provides can see stuff to do with your components under :your-components-namespace/by-id in their app state

cjmurphy17:12:05

But tools (not necessarily libraries, so we might be talking about different things) use a different reconciler. Fulcro Inspect can never name clash because it is a tool.

cjmurphy17:12:43

The only thing it puts in the target/host is an app-id.

adamvh17:12:55

ohhhhhh, I see what you're saying

adamvh17:12:41

I'm talking about generalizing the patterns in fulcro-inspect to produce general re-usable UI components

cjmurphy18:12:45

When you use Fulcro Inspect you can actually pick which 'reconciler' you want to look at the app state of. You will only notice that I guess if you have many clients, for instance with devcards you are adding Fulcro clients.

cjmurphy18:12:15

Right yes - make sense for reusable components.

tony.kay18:12:06

@adamvh I would say one additional thing about ref in the environment. It can be useful, but it can be abused. Transactions are not always run from the thing that the mutation modifies. So, basing your mutations on ref is IMHO a bad idea for many things. I highly recommend passing the ID as a parameter. This encodes it in the mutation so that is clear what is being changed, and encodes it on the wire if the thing goes remote. It lets you use the mutation in a callback at some other place in the UI. You can use (transact! this ref tx), but that means you have to code a table name and ID instead of just an ID. YMMV, but I rarely use ref.

tony.kay18:12:52

So your first example breaks based on where and how you call that mutation...making it only really good for internal mutations that are hidden within components and have no side-effects outside of that component.

adamvh18:12:43

Yeah, what led me to using ref was trying to implement buttons that change which month the user is looking at in a datepicker widget, which is kind of a component-local-state use-case to begin with. I imagine the component as something that component-external mutations interact with basically only by setting the current selected date (and maybe min / max allowable dates).

tony.kay18:12:45

Right. Perfect place to use it

tony.kay18:12:20

and :db/id: if it is a persistent entity in Datomic, that's what you use

adamvh18:12:22

I can see how passing the ID as a mutation param is a better general pattern (and also works here).

adamvh18:12:44

ah, right, i see

tony.kay18:12:52

Using namespaced keywords gets you Clojure spec support

tony.kay18:12:58

sorta like I did in the websocket demo

tony.kay18:12:40

and database IDs rarely need more than one spec...they're usually 64 bit long or UUIDs

cjmurphy18:12:15

Yes I forgot that - that demo was a real eye opener for me as to how spec can work - providing you with names that have all that checking against them.

wilkerlucio18:12:59

@cjmurphy @adamvh the reason I stopped using the by-id convention, is that when you do that, you have to translate that on your server from by-id to id, in Phatom connect I use the attributes to make direct linkable of entities, by dropping the by-id convention and using id directly I'm able to remove any conversion code from one to another, and that's why I prefer just dropping the attribute name directly there

tony.kay18:12:38

@cjmurphy yeah...better than types. More accurate and flexible checks, but none of the inflexibility/boilerplace. Well, types can make things run faster...so, if hard runtime performance guarantees is of higher concern than correctness, simplicity, and cost of development...

wilkerlucio18:12:47

and to be clear about refs, I think they great to use for operations that live on the client-side, when there is server involved I always send the proper id's as part of the params

wilkerlucio21:12:35

@tony.kay can we start accepting empty body on defsc? there are cases we want the component just for the query, and havign an empty body currently doens't compile, so I end up having to write things like:

wilkerlucio21:12:58

(defsc Comp [_ _]
  {:query [:a :b]
   :ident [:a :a]}
  (identity nil))

adamvh22:12:26

on the topic of defsc complaints, it'd be nice if it just kind of magically did the right thing if for :initial-state {:a map}(which is what :query [:a :vector] does, so it's not totally insane of me to expect this)

tony.kay23:12:12

@wilkerlucio thatā€™s been there since beta 5 or something

wilkerlucio13:12:26

oh, thanks, working great šŸ™‚

tony.kay23:12:18

everything is optional except arg list

tony.kay23:12:45

@adamvh That is also present. Read the docsā€¦the children case is super-magical (not insane of you to expect itā€¦since it already does it)

tony.kay23:12:50

:initial-state {:db/id :param/id :child {}} ==> (fn [{:keys [id]}] {:db/id id :child (prim/get-initial-state Child {})}) where Child is derived from the join in the query

tony.kay23:12:11

Iā€™m not advertising that as much, because it is so magical as to be confusing for people

tony.kay23:12:45

@wilkerlucio Changed your patch to ptransact!, which now has the exact calling API as transact! (accepts or derives ref). It is also fixed to work correctly in the presence of mixed local/remote sequences.

tony.kay23:12:25

2.0.0-beta8-SNAPSHOT on clojars. Public API of pessimistic-transact->transaction extended to allow augmenting parsing env of mutations that are deferred. This is used by the main ptransact! to now make sure :ref flows through deferrals.

tony.kay23:12:03

note that ā€œtemplate modeā€ is written to sanity check things. For most components it does the ā€œright thingā€. In some circumstances it is too aggressiveā€¦in that case, you can always drop back to lambda mode for the component that needs looser rules. (Examples are components with initial state and dynamic queries that donā€™t match, root components that want to add in extra stuff on initial state that they themselves donā€™t query)

adamvh23:12:47

oh ... that was more magic than i was expecting, which tripped me up šŸ˜›

adamvh23:12:29

i somehow didn't grok my first time through these docs that it was generating calls to get-initial-state for me

adamvh23:12:42

so i had them in there and as a result there was choking

tony.kay23:12:51

yes, but it kinda has to in order to do the checkingā€¦otherwise you have to worry about general code evaluation

tony.kay23:12:00

I should add an error check that tells you that

tony.kay23:12:12

Please open an issue on thatā€¦I need to get on the road

adamvh23:12:08

aight, will do, thanks for your help, tony!