This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2017-12-14
Channels
- # adventofcode (62)
- # beginners (78)
- # boot (26)
- # boot-dev (9)
- # cider (73)
- # cljs-dev (33)
- # cljsrn (36)
- # clojure (159)
- # clojure-android (1)
- # clojure-austin (1)
- # clojure-greece (79)
- # clojure-italy (10)
- # clojure-nl (3)
- # clojure-russia (11)
- # clojure-spec (33)
- # clojure-uk (26)
- # clojurescript (107)
- # core-async (22)
- # core-logic (12)
- # cursive (16)
- # datomic (13)
- # devcards (5)
- # duct (36)
- # emacs (4)
- # figwheel (3)
- # fulcro (107)
- # graphql (171)
- # hoplon (27)
- # instaparse (24)
- # jobs-discuss (34)
- # juxt (3)
- # lein-figwheel (1)
- # leiningen (8)
- # lumo (11)
- # off-topic (9)
- # onyx (79)
- # parinfer (1)
- # pedestal (75)
- # re-frame (27)
- # rum (1)
- # shadow-cljs (11)
- # spacemacs (20)
- # specter (17)
- # unrepl (96)
@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?
if you give idents to your components, they always know where their own state is, which makes then highly re-usable
and there's a bunch of stuff a datepicker wants to know (e.g. what month the user is looking at right now)
not at all
put everything there, it's the best approach, this way you get the whole trackability story
you can make the whole management inside of the data picker component
and have the parent provide a callback to update the value
like a text field, or any input for that matter
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
there are lots of cool patterns on how to write and manage the components there
no problem š
have fun
@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.
@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}}))
@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?
That's exactly right @tpliliang
@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.
e.g. at https://github.com/fulcrologic/fulcro-inspect/blob/develop/src/fulcro/inspect/ui/data_history.cljs
specifically the return value of ident
called on the first argument of the call to transact!
that initiated the mutation?
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
But what happens when you call the same mutation but without specifying anything for ref
?
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.
I guess when you call transact!
passing in the reconciler rather than this
is when you need to be explicit about what ref
is.
Wonder what happens if you call transact!
with a reconciler as first argument but without putting anything for the ref parameter??
am I doing something wrong when Iām thinking about introducing a component just to get a new ident?
@cjmurphy and you can code your mutations to be defensive about the possibility of a nil ref, e.g. (conj (into [] ref) :keyword)
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?
@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
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.
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
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
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.
which seems like a good pattern to me if you want to make your component into a library
Personally I really like the convention of using :table-name/by-id
. Harder for me to read without that convention.
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
you also give users an indication where this table in their app-state came from, more so than just picking a key
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.
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
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.
I'm talking about generalizing the patterns in fulcro-inspect
to produce general re-usable UI components
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.
@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.
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.
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).
I can see how passing the ID as a mutation param is a better general pattern (and also works here).
and database IDs rarely need more than one spec...they're usually 64 bit long or UUIDs
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.
@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
@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...
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
@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:
(defsc Comp [_ _]
{:query [:a :b]
:ident [:a :a]}
(identity nil))
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)
@wilkerlucio thatās been there since beta 5 or something
oh, thanks, working great š
@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)
: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
Iām not advertising that as much, because it is so magical as to be confusing for people
@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.
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.
@wilkerlucio dev guideās running some as we speak: https://github.com/fulcrologic/fulcro/blob/develop/src/devguide/fulcro_devguide/M30_Advanced_Mutation.cljs#L11
@adamvh http://fulcro.fulcrologic.com/guide.html#!/fulcro_devguide.M05_Defsc_In_Detail
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)
i somehow didn't grok my first time through these docs that it was generating calls to get-initial-state
for me