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)))
AFAIK it should be OK.
@p-himik thanks for validating the approach
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
Please provide some code.
> (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"})))
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
OK, and what is the weird behavior that you're getting?
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)
I tried adding a .preventDefault, but js says e doesnt have a method .preventDefault
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.
ok, thanks. So in general this should work, right? It also works fine in isolation, only messes up when integrated in a bigger app
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
> 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.
ah ok. I'll dig deeper there then. Thanks!
Is there a mechanism in re-frame for subscribing a single function to any events whose event ids have the same keyword namespace?
I don't understand what you're asking. What does "subscribe a function to events" mean?
To have the same handler for multiple event IDs?
If that's the case, then you can use a global interceptor for that.
But there's nothing built-in for that, I believe.
i mean like
(defn handle-event [cofx event]
(case (first event)
::event-a ...
::event-b ...))
(reg-event-fx *ns* handle-event)
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)))
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?
Thanks, I will have a look at your solution š
> the local state
So a regular reagent.ratom/atom
?
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)))])
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.
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:
;; <https://github.com/reagent-project/reagent/blob/master/doc/examples/material-ui.md>
;; Some big discussion on it here:
;; <https://github.com/madvas/cljs-react-material-ui/issues/17>
(reagent.core/flush)
(when on-change
(on-change value)))))]))))
The top level :keys
are there just for the documentation and autocompletion purposes.