Fork me on GitHub
#re-frame
<
2020-12-29
>
jkent15:12:07

I am using a reitit router as a source of page configurations. The following example illustrates my current approach but Iā€™m looking for ideas to improve it. In particular, Iā€™m looking for ways to improve my ::title sub

;; my routes
(rf/reg-sub
  ::dynamic-title
  (fn [db]
    (let [foo (get-in db [:foo :bar])]
      ["Foo" (or foo "...")])))

["/app"
 ["/static-title" {:name        :route-one
                   :title       ["Static" "Title"]
                   :view        ...
                   :controllers ...}]
 ["/dynamic-title" {:name        :route-two
                    :title       [::dynamic-title]
                    :view        ...
                    :controllers ...}]]

;; title view
(rf/reg-sub
  ::title
  :<- [:current-route]
  (fn [{{:keys [title]} :data}]
    (if (-> title first keyword?)
      @(rf/subscribe title) ;; <--- its working but is this ok?
      title)))

p-himik16:12:02

AFAIK it should be OK.

jkent16:12:19

@U2FRKM4TW thanks for validating the approach

šŸ‘ 3
mathias_dw16:12:28

I'm getting weird behavior trying to call re-frame/dispatch in an event handler of a non-react component. Is that something that should be handled differently? I'm setting the event handler in the :component-did-mount part of a type-3 component

p-himik16:12:30

Please provide some code.

mathias_dw16:12:21

> (defn timeline-inner [] > (let [;; Atom with a handle to the js things > ;; Diffing manually here, so keeping a copy of the clj items > tl-state (atom {:timeline nil :dataset nil :cur-items #{}}) > ;; Update function using the props. This is the mutation being pushed > ;; from the re-frame app. > update (fn [comp] > (let [{:keys [items focus]} (reagent/props comp)] > (print (str "PROPS: " (reagent/props comp))) > (when-let [to-add (seq (cset/difference items (:cur-items @tl-state)))] > (print (str "adding " to-add)) > (.add (:dataset @tl-state) (clj->js to-add)) > (print (str "in the dataset: " (js->clj (.getIds ^js/vis.DataSet (:dataset @tl-state))))) > (swap! tl-state update :cur-items into to-add)) > (when focus > (print (str "FOCUS! " focus)) > (.focus ^js/vis.Timeline (:timeline @tl-state) (clj->js focus)))))] > (reagent/create-class > {:reagent-render (fn [] > [:div > [:h4 "Timeline"] > [:div#timeline-canvas {:style {:width "100%"}}]]) > :component-did-mount (fn [comp] > (let [canvas (.getElementById js/document "timeline-canvas") > ds (new js/vis.DataSet) > _ (when-let [pts (clj->js (seq (:items (reagent/props comp))))] > (.add ds pts)) > tl (js/vis.Timeline. canvas > ds > #js {})] > (reset! tl-state {:timeline tl > :dataset ds > :cur-items (:items (reagent/props comp))}) > (.on > tl "rangechanged" > (fn [e] > (let [ev (js->clj e)] > (re-frame/dispatch [::events/set-timeline-range > (get ev "start") (get ev "end")])))))) > > :component-did-update update > :display-name "timeline-inner"})))

mathias_dw16:12:24

I've asked you before about the timeline stuff, and you told me to use react refs instead, but I didn't understand after reading up. This code does more than the event handling part at the end, but all the rest works ok

p-himik16:12:40

OK, and what is the weird behavior that you're getting?

mathias_dw16:12:42

What i'm seeing in the complete app is that the ::events/set-timeline-range gets called (it just prints something now), but then random things happen and my app-state gets changed in totally unrelated way (the top-level view element)

mathias_dw16:12:27

I tried adding a .preventDefault, but js says e doesnt have a method .preventDefault

p-himik16:12:13

Sounds like the error is somewhere else then if dispatch is called. (.on tl ...) is probably not a proper JS event machinery. And the e argument is likely not an event but just some data - otherwise (js->clj e) would just return e (it doesn't convert JS objects with constructors, like events). Just in case, you may try dispatch-sync instead of dispatch. Other than that, no idea.

mathias_dw16:12:10

ok, thanks. So in general this should work, right? It also works fine in isolation, only messes up when integrated in a bigger app

mathias_dw16:12:09

I was mainly worried that for some reason you shouldnt call re-frame/dispatch from a "native" event handler, but I didn't find anything in the docs

p-himik16:12:37

> So in general this should work, right? Yep > I was mainly worried that for some reason you shouldnt call re-frame/dispatch from a "native" event handler There are no such reasons. But also that (.on tl "rangechanged" ...), as I mentioned, is not a "native" event handler. It's just something that js/vis.Timeline implements - it can do whatever it wants.

mathias_dw16:12:15

ah ok. I'll dig deeper there then. Thanks!

šŸ‘ 3
emccue17:12:05

Is there a mechanism in re-frame for subscribing a single function to any events whose event ids have the same keyword namespace?

p-himik17:12:36

I don't understand what you're asking. What does "subscribe a function to events" mean?

p-himik17:12:13

To have the same handler for multiple event IDs?

p-himik17:12:47

If that's the case, then you can use a global interceptor for that.

p-himik17:12:03

But there's nothing built-in for that, I believe.

emccue18:12:37

i mean like

emccue18:12:56

(defn handle-event [cofx event]
  (case (first event)
    ::event-a ...
    ::event-b ...))

(reg-event-fx *ns* handle-event)

p-himik18:12:30

Yes, I've answered it above. But if you know the set of all event IDs in advance, then just doseq over them:

(let [handler (fn [cofx event] ...)]
  (doseq [event-id [::event-a, ::event-b, ...]]
    (reg-event-fx event-id handler)))

sveri21:12:09

Hi, I have a re-frame component with local state. That component uses material ui text fields with on-change handlers, updating the local state. Typing fast will lead to lost characters in the state. IIRC this is a known problem when using the re-frame state, but with local state this is new to me. Did someone see that before? Am I missing something?

p-himik21:12:09

> the local state So a regular reagent.ratom/atom?

sveri21:12:31

Yea, exactly. I just went through these answers here: https://day8.github.io/re-frame/FAQs/laggy-input/ and knew that using local state should fix that. but it does not in my case. I am not sure if that is because of how I create my component:

(defn user-login-panel []
  (let [state (reagent/atom {:email "" :password ""})]
    (fn [{:keys [^js classes]}]
      [mui/container    
       [:form#login-form
        [mui/text-field {:value     (:email @state)
                         :on-change #(swap! state assoc :email (u/get-value-of-event %))}]]])))

(defn user-login-panel-wrapper []
  [:> (with-styles (reagent/reactify-component (user-login-panel)))])

p-himik22:12:43

I've had some problems with Material UI text inputs as well, although I recall only problems with losing cursor position upon editing. In any case, Reagent docs (or maybe examples) have a section that describes how to work with Material UI text inputs in particular.

p-himik22:12:47

I ended up creating my own wrapper for it:

(def -text-field (reagent.core/adapt-react-class TextField))

(defn text-field [{:keys [auto-complete auto-focus classes default-value disabled
                          error FormHelperTextProps full-width helper-text id
                          InputLabelProps InputProps input-props input-ref label
                          margin multiline name on-change placeholder required
                          rows rows-max select SelectProps type value variant]}]
  (let [external-model (reagent.core/atom (deref-or-value value))
        ;; Need a default non-nil value to avoid React error
        ;; about switching from uncontrolled to controlled input.
        internal-model (reagent.core/atom (if (nil? @external-model) "" @external-model))]
    (fn [props]
      (let [;; Deep `js->clj` cannot be used here because it will make all refs invalid.
            ;; The reason is that refs are expected to be mutable objects and a
            ;; `js->clj` -> `clj->js` round-trip creates a completely new object.
            props (cond-> props (object? props) shallow-js->clj-props)
            latest-ext-model (deref-or-value (:value props))
            on-change (:on-change props)]
        (when (not= @external-model latest-ext-model)
          (reset! external-model latest-ext-model)
          (reset! internal-model latest-ext-model))
        [-text-field
         (assoc props
           :value @internal-model
           :on-change (fn [evt]
                        (let [value (oget evt :target.value)]
                          (reset! internal-model value)
                          ;; The flush is needed to prevent cursor jumping when editing the value
                          ;; in the middle. Not sure why the flush is preventing it though.
                          ;; Reagent repo has a different example but it delves into some
                          ;; internals of Material UI and I don't really like that approach:
                          ;; 
                          ;; Some big discussion on it here:
                          ;; 
                          (reagent.core/flush)
                          (when on-change
                            (on-change value)))))]))))

p-himik22:12:08

The top level :keys are there just for the documentation and autocompletion purposes.

sveri09:12:19

Thanks, I will have a look at your solution šŸ™‚