7  Nexus

(ns oiiku.ui.docs.nexus
  (:require
   [scicloj.kindly.v4.kind :as kind]))

We use nexus for event/effect handling.

7.1 Known (potential) problems

Known problems we have encountered with nexus, and how to fix/avoid them.

7.1.1 Nexus uses postwalk for its placeholders

For very large data structures this becomes a real problem with slow interactions, as the postwalk goes through the entire datastructre.

Solve this by being smart about what data is sent in. Let the effect handle the heavy load, and only send in what is absolutely necessary. Avoid large sequences of maps, as that creates a lot of space to postwalk through.

7.1.2 Delayed evaluation of placeholders

When writing placeholders that need to go through other actions/effects, before finally being plugged in to the machinery, nexus will evaluate the placeholders, as part of the machinery of sending actions/effects through the nexus machinery.

(def placeholders
  {:third-party-library/click
   (fn [dispatch-data]
     (if-let [res (some-> dispatch-data :replicant/dom-event .-detail)]
       ;; if we find a result, return the result
       res
       ;; else return a carbon copy of the placeholder
       [:third-party-library/click]))})

The above placeholder will return itself if it doesn’t find anything, passing through any other actions/effects that is meant to setup an evaluation of the action/effect, but at a later stage. Keyboard bindings is a good example, where we want to setup the effect for specific key combinations, and we do this through the nexus actions/effects system.

7.1.3 Interop with third party libraries

When running against third party libraries, that handle interactivity via callback functions, we can use custom js DOM events (js/CustomEvent) to create new events, that will allow us to reach replicant with the data we need.

(kind/mermaid
 "flowchart TD
  native(Native DOM Event) --> action(nexus Action fn)
  action -->|0-n effects| effect(nexus Effect fn)
  custom(Custom DOM event) --> action
  effect -->|0-n stateful changes | stateful(Stateful change in browser)
  effect --> |New effects| effect
  custom --> js-event-handler
  native --> js-event-handler
  js-event-handler(nexus Effect fn with js/addEventListener) -->|dispatch creates new DOM Event| action ")
flowchart TD native(Native DOM Event) --> action(nexus Action fn) action -->|0-n effects| effect(nexus Effect fn) custom(Custom DOM event) --> action effect -->|0-n stateful changes | stateful(Stateful change in browser) effect --> |New effects| effect custom --> js-event-handler native --> js-event-handler js-event-handler(nexus Effect fn with js/addEventListener) -->|dispatch creates new DOM Event| action

We can capture events via replicant’s event mechanism, and for the most part everything just works. There is one gotcha when we go from a js/addEventListener function back into replicant/nexus. In order to go back into nexus we need to use the dispatch function that comes into the effect fn thas has the js/addEventListener setup. That dispatch function has closed over the effect fn DOM event. The js/addEventListener DOM event that came in the js/addEventListener fn is a different DOM Event, but this is what we want to send forward as the DOM event for the dispatch function, not the effect fn DOM Event that is closed over inside dispatch. This kind of shadowing of DOM events is not supported by nexus.

This dilemma can be solved by sending the data from the js/addEventListener DOM Event as part of the context map. This in turn causes a new (minor) problem, where we now need to look for data inside the :dispatch-data key for any action/effect/placeholder that is dependant on events further downstream.

source: bases/admin-web/src/oiiku/ui/docs/nexus.clj