Fork me on GitHub
#fulcro
<
2018-07-16
>
montanonic08:07:56

Hey Tony and all. First off, Fulcro is really wonderful, and the documentation is really exemplary across the board; thank you so much. I'm working on a hand-rolled drag-and-drop implementation, and I'm having issues right now. Without diving into those, I wanted to sanity-check my approach, and follow-up with bugs after I do that. Here's what I have so far:

montanonic08:07:44

My design expects only one component to be dragged at a time, and therefore, any state coupled to dragging should be at the root of the tree; it's not component specific. Hence:

(defonce app (atom nil))
...
(defcard-fulcro card-list-live
  Root
  {}
  {:inspect-data true
   :fulcro       {:started-callback #(reset! app %)}})
That way I can use #(prim/transact! (:reconciler @app) ... to make changes to the root from any component mutation.

montanonic08:07:34

For the component part of the dragging effect, fundamentally I just need an onMouseDown event handler to note its initial location and flag it as a drag if the mouse moves a particular distance; then to update the style with position: absolute; top: ...; right: ....

montanonic08:07:21

Rather than rolling this out for each draggable component (which frankly wouldn't be that much code, but is clearly not the most elegant approach) it seemed most fitting to create a HOC (higher order component) that wraps a div around a draggable component:

(defmutation on-pointer-down
  "To be used on any mousedown/touch event on a draggable object."
  [{:keys [event]}]
  (action [{:keys [state]}]
    (swap! state assoc :draggable/pointer-down-position (u/client-x&y event))))

(def draggable-next-id (atom 0))
(defsc Draggable [this {:keys [db/id]}]
  {:query         [:db/id]
   :initial-state (fn [] {:db/id (swap! inc draggable-next-id)})
   :ident         [:draggable/by-id :db/id]}
  (let [children (prim/children this)
        opd      #(prim/transact! (:reconciler @app)
                    `[(on-pointer-down {:event ~%})])]
    (dom/div :.draggable {:onMouseDown  opd
                          :onTouchStart opd}
      children)))
The unique ids here are so that the specific instance being dragged can have its wrapping div's position changed via updates to the inline style (I haven't yet created the mutation for that).

montanonic08:07:01

Finally, it looks like this when used (this is the Render body for one of my draggable components): (ui-draggable {} (dom/div :.card text)) Now, I'm getting runtime errors with this design, but before I get into that tomorrow: does anything jump out to anyone here as problematic, whether it be a technical issue or questionable design?

montanonic09:07:27

That was a lot; thank you in advance for your time and any feedback.

wilkerlucio12:07:22

@montanonic is there a reason you can't use some external library to deal with the drag-drop? I'm asking because D&D is not a simple feature to implement (specially if you want to it to perform well), unless you really understand what you are doing, I recommend dont, instead you can delegate that to some good implementation like react-dnd http://react-dnd.github.io/react-dnd/

tony.kay13:07:13

@montanonic Assuming you don't want to use a library or HTML5 drag-n-drop support: The HOC pattern is described in the book. http://book.fulcrologic.com/#_react_higher_order_components There is an extra step because of some internal dynamic vars. It's ok to transact against the reconciler, and that will cause a root render. Root renders will re-run the query.

tony.kay13:07:48

If you need that data in a bunch of components then you might also look into :shared and :shared-fn (which only update on root refresh, BTW) http://book.fulcrologic.com/#SharedState

tony.kay13:07:18

If you're seeing errors, I'm betting the HOC is your problem, which is correctable as described in the book

tony.kay13:07:04

Those bindings are what I was referring to, and they are needed if you "do something" with the element that isn't part of Fulcro rendering...like transition libraries do...they might clone/re-render the low-level element outside of mainline rendering.

roklenarcic18:07:13

recently I've started using Specter which boasts that it's much faster than update-in and get-in. I wonder if Fulcro could get a performance boost from that. https://github.com/nathanmarz/specter/wiki/Specter-0.11.0:-Performance-without-the-tradeoffs

tony.kay20:07:47

@roklenarcic The problem is the performance gains are due to path compilation, and Fulcro’s paths are all dynamic…so I think it ends up being slower for the things that matter

tony.kay20:07:58

I think…not sure. I’m not an expert on specter

roklenarcic20:07:46

As far as I understand, the new version (0.11+) has achieved that performace without compilation.

roklenarcic20:07:18

Or with less of a difference

roklenarcic20:07:44

I just suggested a second look, as it seems that things have changed for that library.

roklenarcic20:07:54

In any case I don't have performance problems as such so it's not a big deal.

montanonic20:07:00

Wiliker, thanks for your feedback. This is a hobby project where 1/2 of my interest is in building my app, but the other 1/2 is in building proficiency with Clojure, Fulcro, and my own library-making abilities, which I greatly enjoy doing. DnD is an interesting problem to me and it's fun to work on implementing it. Tony, thank you very much for the help; I'll focus on those sections of the book and see if I can fix my code.

wilkerlucio21:07:50

@montanonic sure, it can be a lot of fun if you have the time, so if I wanna go that direction I have a few tips: when doing things in animation fashion (like dragging something around), to get it to perform well you must take control over that node directly, that means don't use react lifecycle to update it, thats slow, instead of you get a hold of the node and manipulate yourself, once you are to finish the drag process (or maybe using a debounce) you sync back with the react state, this flow can give production grade speed

👍 4
montanonic21:07:01

That's helpful. I was considering using fulcro events to add the mouse position as a key on the root, which the dragged component would use to set its position via the style attribute. So you'd suggest instead to move that state and calculation outside of fulcro/react state and updates, and then resync after? I'm just trying to make sure I understand what you mean by using the lifecycle, and to see if my original plan above would be doing that, and therefore be slow.

wilkerlucio21:07:54

yes, you got right, move that state and calculation outside fulcro/react and resync later

👍 4
montanonic21:07:48

Great. Thanks a lot for the info wilker.

montanonic21:07:01

That's helpful. I was considering using fulcro events to add the mouse position as a key on the root, which the dragged component would use to set its position via the style attribute. So you'd suggest instead to move that state and calculation outside of fulcro/react state and updates, and then resync after? I'm just trying to make sure I understand what you mean by using the lifecycle, and to see if my original plan above would be doing that, and therefore be slow.

montanonic21:07:07

I updated my fulcro dependency from 2.5.9 to 2.6.0-rc3. I'm getting this error on the server

Mon Jul 16 14:57:32 PDT 2018 [worker-2] ERROR - GET /js/cards/cljsjs/showdown/development/showdown.js.map
java.lang.IllegalArgumentException: Cannot open <nil> as an InputStream.

montanonic22:07:04

The behavior seems to be that I get ERROR - GET /intl-messageformat-with-locales.min.js.map when I refresh the page on localhost:3000, the dev part, and ERROR - GET /js/cards/cljsjs/showdown/development/showdown.js.map when refreshing on localhost:3000/cards.html. I have two repls, one with Figwheel Cards + Dev, and other with the server that's giving these errors. It seems like the only issue is that I won't be getting some source maps; everything that I care about looks fine. Nonetheless, I don't like getting errors, so wanted to ask what might be up.

tony.kay22:07:03

@montanonic That sounds like more of a potential shadow-cljs/package.json kind of issue. RC3 changes nothing about that

montanonic22:07:20

Hmm, not using shadowcljs

tony.kay22:07:31

ah, ok… 🙂

wilkerlucio22:07:44

showdown ~ shadow XD

wilkerlucio22:07:55

thats a dep required by Devcards

tony.kay22:07:59

could be that the two cljsjs things you’re using don’t have source maps in them

montanonic22:07:32

Just using [cljsjs/react "16.4.0-0] [cljsjs/react-dom "16.4.0-0"]

montanonic22:07:23

No worries about this doesn't seem to affect me in any negative way. But I wasn't getting this before updating fulcro from 2.5.9

montanonic22:07:33

I did not update anything else or change code

tony.kay22:07:43

could be the upgrade to fulcro also got you an update to figwheel/devcards?

tony.kay22:07:33

in any case: it’s just missing source maps, which aren’t part of Fulcro for those anyhow

👍 4
montanonic22:07:00

Cool. Thanks for the help. I'll maybe look into this later and see if I can figure out what changed.

montanonic23:07:55

I've made a bit of conceptual progress on my earlier Drag-and-drop oriented questions. First off: stupid typo, I was using (-.clientX event) instead of (.-clientX event) in my code. Now events fired by my Fulcro "wrapping component" work (because the underlying code isn't broken, hah). Second: I've still had to continue wrapping my head around how initial state works. I'm used to initializing certain props in React inside of its constructor, and assumed :initial-state (fn [_] ...) would run when passing props to (ui-some-component some-props ...), but actually the initial-state function will only run when get-initial-state is used. So if I have a wrapper like ui-draggable, and a component that uses it like (ui-draggable {} (dom/div "I'm another component)), no props will be initialized in ui-draggable, even if its :initial-state has default values initialized.

wilkerlucio23:07:33

that's correct, initial-state is not about component state, it's about database state (fulcro db state)

montanonic23:07:52

Now the next thing for me to tackle is initializing component state without having to query it, so I'm thinking I could actually use componentDidMount for this?

montanonic23:07:00

Or would that just be a bad path to go down?

wilkerlucio23:07:11

bad path, you should initialize it previosly

wilkerlucio23:07:20

on app start, or when you create a new data entry for that on the db

montanonic23:07:38

What if my component state is just generating a unique id, that's it?

wilkerlucio23:07:55

still should go though initial state

wilkerlucio23:07:05

remember the view is a reflection of your db state

montanonic23:07:17

I want to wrap some ui elements in draggable without them having to change their query or initial state code, that's the reason I'm trying to figure this out

wilkerlucio23:07:17

so the DB must be initialized before your components

montanonic23:07:46

But also what I'm trying to achieve might be simply wrong to do in Fulcro, which is fine.

montanonic23:07:19

Okay, I think I actually have an idea here. Thanks for your feedback, that helps me to understand what's going on.