Fork me on GitHub
#fulcro
<
2017-09-03
>
tony.kay04:09:19

So, I’m working on a convenience macro for the common case. So far, I’ve got this working:

(defsc Person
  "A person component"
  [this {:keys [db/id person/name]} _ _]
  {:table         :PERSON/by-id
   :props         [:db/id :person/name]
   :initial-state {:db/id :param/id :person/name :param/name}}
  (dom/div nil
    name))

(def ui-person (om/factory Person {:keyfn :db/id}))

(defsc Root
  [this {:keys [people ui/react-key]} _ _]
  {:props         [:ui/react-key]
   :initial-state {:people [{:id 1 :name "Tony"} {:id 2 :name "Sam"} {:id 3 :name "Sally"}]}
   :children      {:people Person}}
  (dom/div #js {:key react-key}
    (mapv ui-person people)))

tony.kay04:09:17

The basic syntax is:

(defsc ComponentName
   optional-docstring
   [thissym propsym computedsym children]
   options-map
   body)

tony.kay04:09:01

where options-map can be used to generate the query, ident, and initial state. It does not support more advanced cases…defui is really the general utility. Just trying to save some headaches for the very common component case

tony.kay04:09:12

options-map: - :children (optional) a map of keyword to class. Generate the query join, error checks your destructuring of props, and helps initial state generation work - :props a vector of the scalar props. Generates query elements, and error check destructuring - :id (optional, defaults to :db/id). Needed for ident. - :table (optional) The table side of the ident. If not supplied, you don’t get an ident. - :initial-state (optional). A map of key/value. Checked against queried props. Value for scalar props can be a scalar value OR the special :param/XYZ, which will pull the value from incoming parameters. Value(s) for children can be a map or a vector of maps which are treated as parameters to an automatic call of (get-initial-state ChildClass p). The value(s) in the option map(s) can be scalars or :param/XYZ.

tony.kay04:09:40

I’m still playing with it, and am not ready to release, but if people have feedback/input before it becomes API I’d like to hear it.

tony.kay04:09:38

It is on branch feature/defsc, with a devcard of convenience-macro-cards

tony.kay04:09:10

Oh, and it does the ^:once bit for you as well. Thinking about adding in automatic generation of a deref factory with react key based on id (if there is an ident)…so you could do this: (@Person person-props). Still thinking about the options here. I think I fancy a deffactory more, since you can name the symbol then, and possibly have a bit more control. Thinking on it.

tony.kay04:09:31

not sure it is worth it

tony.kay04:09:45

The error checking is quite nice, actually…it makes it much harder to write broken UI code.

tony.kay04:09:08

I have to say: after playing with it a bit, I really like it. It caught more than one mistake I made as I coded up the devcards for it, and reported the problem in an easy-to-understand way.

tony.kay04:09:27

should have written this a year ago

tony.kay05:09:47

I just pushed fulcro-1.0.0-beta10-SNAPSHOT with the new macro (fulcro.client.core/defsc). That will allow anyone to play with it easily to give feedback. NOTE: The API for it is not considered stable, but it is fairly well-covered by tests, and seems to work well as far as I’ve tried it.

claudiu10:09:44

@tony.kay Cool, makes total sense. Probably should not have mentioned component, the thing that I seem to be repeating is sending the component ident (ref). Mostly use post-mutation for loading flags, that i have in the component. currently send it as param, to avoid hard-coding it in the mutation.

tony.kay16:09:31

I see. The ref is possibly somethign that could be automatically included, since it is data.

tony.kay16:09:13

Hm. Composing in mutations is a problem…there is no way to know ref in load-action, for example

tony.kay16:09:42

Yeah, it’s really not a good idea I don’t think to mix :ref into the post mutation environment. It would make people expect it there, and there are just too many circumstances when it cannot be supplied:

(load @app ...) 
(load reconciler ...)
(load parent-component-during-callback ...) ; wrong one
Inside mutations:
(load-action app-state-atom ...)

tony.kay16:09:15

Frankly, it would be easier for you to write a wrapper for load that automatically augments your post-mutation parameters with a standard parameter. Then you’d know the limitations, and have the convenience.

tony.kay16:09:28

@claudiu This is untested, but should be close:

(defn refload [component-instance keyword-or-ident QueryClass config]
  (let [ident  (when #?(:clj  (satisfies? om.next/Ident component-instance)
                        :cljs (implements? om.next/Ident component-instance))
                 (om.next/ident component-instance (om.next/props component-instance)))
        config (update config :post-mutation-params assoc :ref ident)]
    (load component-instance keyword-or-ident QueryClass config)))

tony.kay16:09:56

and should only ever be called with a this from within a component

tony.kay16:09:18

Actually, it should work just as well as load…ref just won’t show up if you use a reconciler or app

tony.kay16:09:28

or a component that has no ident

tony.kay16:09:34

Namespaces:

[om.util :as util]
[fulcro.client.data-fetch :as df]
[om.next :as om]
If using cljc you’ll need to import Ident

tony.kay16:09:59

I tuned it up a bit…update tolerates nils well, so all that checking wasn’t necessary

claudiu16:09:32

thank you, will give it a try. 🙂

claudiu16:09:33

It's a really small thing, was curious why it's not there if it's a design decision. Makes sense not to add it given load-action 🙂

claudiu16:09:48

Still a lot of details to figure out. So far think I'm getting close to breaking even for this project, if I would have used js & python. Initial learning curve but things are going really smoothly and fast now 🙂

tony.kay16:09:41

good to hear

tony.kay17:09:36

of course you can just call that function load and make your own data fetch namespace 🙂

claudiu17:09:22

ahh yes 🙂 Don't know it the benefits would benefits will outweigh the cons. So far everything is almost perfect.

claudiu17:09:42

Only the fact that I can't use load on ssr seems to be missing. Still pretty awesome to have ssr, user experience is really nice 🙂

tony.kay17:09:27

So, load on SSR…I think all you’d need to do is write a mock networking implementation for the server-side…I think it would be pretty small.

claudiu17:09:20

hmm would be crazy if it would work 🙂. Really pressed for time until the end of this month, but will give it a try afterwords.

tony.kay17:09:37

You’re currently my biggest contributor on patreon, so I may just try it for you later today 😉

claudiu17:09:58

was thinking of custom networking like in the restful api in getting started, and just hook it up there with the fulcro-server. Is that what you mean or mocking as in testing ?

tony.kay17:09:13

the former…it is just a protocol with a send method…instead of doing network stuff, call fulcro-parser

tony.kay17:09:39

there’s no network error possible, so it’s very little code I think…

tony.kay17:09:59

assuming I haven’t stubbed out something in the network stack that would break it

tony.kay17:09:38

oh wait…it is even easier

tony.kay17:09:54

the clj version of load could just call fulcro-parser directly!

tony.kay17:09:19

don’t even have to involve any of the networking stack

tony.kay17:09:53

there’s a little complication to it…load-action, etc

tony.kay17:09:18

it would be better to have all the machinery in place, so I take that back

claudiu17:09:00

the implications if it would work are pretty nice. Just put in componentWillMount the initial loads, for ssr should have a fully populated page out of the box, with the same code that you use on frontend when doing routing.

tony.kay17:09:03

there are several bits that may be non-trivial. I’ll have a look at it.

tony.kay17:09:21

oh, no, you won’t get react lifecycle

tony.kay17:09:23

react doesn’t run on the server

tony.kay17:09:38

the string conversion process isn’t full-blown react

tony.kay17:09:46

you’ll get started-callback.

claudiu17:09:27

in nodejs componentWillMount is the only one that runs on ssr. No idea if om-next mimics this behavior.

tony.kay17:09:56

don’t know

tony.kay17:09:13

not sure how the render to string is working there. I haven’t read the code.

claudiu17:09:11

my ideal flow, would be a static method initialLoad, that the router can call.

claudiu17:09:39

starter callback where I can call component static method that can use load inside sound really nice 🙂

claudiu17:09:06

thank you for taking the time to look over it 🙂

tony.kay19:09:10

@claudiu As I thought: rendering to string is written in om.next by hand. There are no lifecycle calls

claudiu19:09:57

so not exact react behavior. In the react docs it says "Generally, we recommend using the constructor() instead." & figwheel also seems to trigger the method for every change to the file.

tony.kay19:09:54

remember that figwheel changes the root level key, meaning react will throw away the DOM an rebuild it…which will trigger all lifecycle mounts I thnk

tony.kay19:09:18

another reason to embed your load behaviors into mutations…avoid the UI-based side-effects

tony.kay19:09:56

I never touch the react lifecycle unless I need it for the DOM…focus, ref to a canvas, that kind of stuff

claudiu19:09:30

but apart for componentWillMount and routing. Is there another place where to put the initial loading markers, and load ?

tony.kay19:09:29

what do you mean? Why isn’t started-callback sufficient?

tony.kay19:09:16

make your own routing mutations…the routing namespace has functions for composing routing into your own mutations

claudiu20:09:10

ahh, don't know why I did not think of extending the routing mutation.

tony.kay20:09:13

😄 Sorry, that is the intended use

tony.kay20:09:29

that way your routing mutation can check state, see what is stale, etc etc.

tony.kay20:09:38

completely moves the side-effects to the proper place: mutations

tony.kay20:09:59

Hm. That isn’t in the dev guide, is it 😞

tony.kay20:09:11

I could swear I wrote that down in docs somewhere

tony.kay20:09:21

route-to-impl! will work for composition when using all kinds of routers (including dynamic). update-routing-links works for just the standard union-based.

claudiu20:09:36

seems to be in the source, added with dynamic router 🙂

tony.kay20:09:02

well, update-routing-links was there since defrouter

tony.kay20:09:15

the new route-to-impl! was needed to support dynamic

tony.kay20:09:42

I’m adding some comments to section M15 of the dev guide

claudiu20:09:51

totally missed that idea. Will move the initial load into routing mutations, for most of my use-cases think dynamic router is a better match. Haven't had time to look over it, as soon as I got routing working, focused my attention on other areas.

claudiu20:09:02

fulcro-template with full server configuration was a real life-saver 🙂

tony.kay23:09:22

re-pushed SNAPSHOT of beta10 to clojars. I’ve hammered on defsc quite a bit, and think it is pretty close to API-ready. I fixed up initial state parameterization support, and documented it in section M05 of the devguide (not live, just on develop). It really makes writing most components a lot cleaner and easier. It could easily be exnteded to support react lifecycle as well, which would go a long way towards making it the only solution you’d need. The API it has should still be considered relatively unstable.

tony.kay23:09:43

@currentoor @mitchelkuijpers @wilkerlucio @gardnervickers ^^^ I think you guys will like this, assuming you’re not using a lot of custom protocols on your components.

cjmurphy23:09:00

Will it be compatible with forms spec etc?

tony.kay23:09:18

Good question 🙂

tony.kay23:09:31

at the moment it is not, but it isn’t even officially released yet 😉

cjmurphy23:09:51

Yeah i might like it if it was 😜

tony.kay23:09:54

Would be trivial to add that. Thanks for asking

tony.kay23:09:13

oh…iniitial state might be troublesome, since form lifecycle can be complex

tony.kay23:09:45

then again…if you make it a form, it’s just wrapping it in build-form, isn’t it?

tony.kay23:09:57

can’t remember if that’s recursive

tony.kay23:09:11

oh, doesn’t need to be…the sub-forms would auto-wrap it as well

tony.kay23:09:41

See, I knew someone would have good feedback @cjmurphy

tony.kay23:09:41

@cjmurphy Added. Now specify :form-fields [...] with your form field declarations. If you use initial state, it will auto-wrap it with build-form. I didn’t test it live, but the macro output looked right.

tony.kay23:09:51

pushed to clojars on beta10-SNAPSHOT