Fork me on GitHub
#hyperfiddle
<
2024-01-14
>
wei00:01:17

i'm trying to scroll to a new item after it's been added. what's a good way to do this after the DOM updates?(see TODO)

#?(:clj (defonce !items (atom [])))
(e/def items (e/server (e/watch !items)))

#?(:clj
   (defn add-item! [id]
     (swap! !items conj {:item/id [id]})))

(e/client
 (div
  (ui/button
   (e/fn []
     (e/server (add-item! (util/uuid)))
     ;; TODO: wait for item to be added and fetch its dom node
     (.scrollIntoView new-dom-node))
   (text "Add an item")))
 (e/for-by :item/id [{:item/keys [title]} items] (div title)))

Vincent00:01:47

Hmm how could we watch for a dom addition :thinking_face:

henrik09:01:24

When we do stuff like this, we usually stick the scroll logic in the item to be scrolled to itself. This is because race conditions become a non-issue, and because it’s the “correct” place in terms of colocation. Of course, it requires some way of knowing that an item is new though, or it’ll run on every page load.

👍 1
Geoffrey Gaillard10:01:08

IntersectionObserver and MutationObserver might help

👀 1
wei18:01:09

That's a great idea! Will try it.

wei00:01:18

works! in the button handler, i add the item id to a set tracking new items:

(swap! !new-items conj new-id)
and the item handles the scrolling and removing itself from the new item set
(when (@!new-items id)
  (when-let [node (.getElementById js/document id)]
    (.scrollIntoView node))
  (swap! !new-items disj id))
is there a way to reference the DOM node within its electric function, without resorting to getElementById?

🙌 1
henrik07:01:50

(when (@!new-items id)
  (.scrollIntoView dom/node)
  (swap! !new-items disj id))

henrik07:01:57

If you e/watch !new-items instead of dereferencing it, you effectively have a way to scroll to an item at any point in time, not just when initially adding them. Just a note for the future.

henrik07:01:54

Make sure to e/watch once and pass down the value to the children in that case. Don’t e/watch once per child.

wei05:01:00

@U06B8J0AJ would you mind elaborating on this e/watch !new-items being able to scroll to an item at any point in time? i thought about it a bit and still don't get it, but it seems interesting. maybe a (pseudo-)code snippet to illustrate?

henrik07:01:05

Sure! I only mean that scrolling doesn’t necessarily have anything to do with adding. For example:

(e/defn Bar
  [should-scroll]
  (dom/div
    (when should-scroll
      (.scrollIntoView dom/node))))


(e/defn Foo
  []
  (let [!scroll-to (atom nil)
        scroll-to  (e/watch !scroll-to)]
    (e/for-by :id [{:keys [id]} [{:id 0} {:id 1} {:id 2}]]
      (Bar. (= id scroll-to)))))
You can set !scroll-to to an ID and have it scroll for any reason. For example when adding an item, but you could also set it from the URL: my.app/?focus=1 or something, to support direct linking.

🙏 2
wei08:01:03

makes sense, and that still circumvents the race condition issue from before. brilliant!