reset! on reagent atom doesn't work as intended - clojurescript

So what I'm trying to do is a basic tabbed view using re-com's horizontal-tabs element. I added a v-box element and below that i want to have my tabs element and the body that corresponds to the tab. although on the :on-change i call reset! on the model of the horizontal-tabs and it doesn't seem to work.
(defn left-panel []
[re-com/box
:size "auto"
:child (let [selected-tab-id (r/atom (:id (first left-panel-tabs-definition)))
change-tab #(reset! selected-tab-id %)]
[re-com/v-box
:children [[re-com/horizontal-tabs
:model selected-tab-id
:tabs left-panel-tabs-definition
:on-change change-tab]
[(left-panel-tabs #selected-tab-id)]
]])])
(defn forms-view []
[:div "Forms View"])
(defn swagger-view []
[:div "Swagger View"])
(def left-panel-tabs
{::swagger #'swagger-view
::forms #'forms-view})
(def left-panel-tabs-definition
[{:id ::forms
:label "Forms"
:say-this "Forms View"}
{:id ::swagger
:label "Swagger"
:say-this "Swagger View"}])
If instead of
[(left-panel-tabs #selected-tab-id)]
i do something like
(do (log #selected-tab-id) [(left-panel-tabs #selected-tab-id)])
it'll always print the value that i've set my reagent atom at the beginning (in this case ::forms)

Thanks to u/Galrog over at reddit I realised that in order for this to work i need to create a Form-2 component (3 forms of creating a component http://reagent-project.github.io/docs/master/CreatingReagentComponents.html#the-three-ways)
Changing my left-panel component to a Form-2 component re-renders both children on the atom's change
Code:
(defn left-panel []
(let [selected-tab-id (r/atom (:id (first left-panel-tabs-definition)))
change-tab #(reset! selected-tab-id %)]
(fn []
[re-com/box
:size "auto"
:child [re-com/v-box
:children [[re-com/horizontal-tabs
:model selected-tab-id
:tabs left-panel-tabs-definition
:on-change change-tab]
[(left-panel-tabs u/selected-tab-id)]]]])))

Related

Passing data between reagent components

I have the following reagent components
(defn comments-component [arg-id]
[:p arg-id])
(defn arguments-component [list-of-args]
[:ul.arguments
(for [{:keys [comment upvotes id]} #list-of-args]
^{:key id}
[:li
[:p comment]
[:p upvotes]
[:p id
[modal-button :argument
;title
"Comments"
;body
[:div
[:p "Comments"]
[comments-component id]]
;footer
[:p "OpinionNeeded"] ]]])])
(defn debate []
(let [debate-topic #(rf/subscribe [:debate/topic])
debate-affirmatives (rf/subscribe [:debate/affirmatives])
debate-negatives (rf/subscribe [:debate/negatives])]
(fn []
[:div
[:a {:href "/"} "Return home"]
[:p "This page is being worked on"]
[:div
[:h2 (get debate-topic :title)]
[:p (get debate-topic :description)]]
[:div.columns
[:div.column.is-half
[:p "Agree"]
[arguments-component debate-affirmatives]]
[:div.column.is-half
[:p "Disagree"]
[arguments-component debate-negatives]]]])))
The problem I'm encountering is that the modal button is supposed to create a modal popup with the id for each specific argument, (which I can then use to fetch the comments for that specific argument.)
But instead, I'm getting this bug whereby all the modals for different arguments show the same id.
I cannot figure out why this is happening, but it seems that all the modal-button functions are getting called with the id of the last or first argument to be rendered.
This is what the relevant portion of app-db looks like on this page
:affirmatives [{:id 1, :comment "", :upvotes 0, :topic_id 2, :affirm true} {...}]
:negatives [{:id 2, :comment "", :upvotes 0, :topic_id 2, :affirm true} {...}]
:comments [{:id 1, :comment "", :upvotes 0, :argument_id 1, :topic_id 2} {...}]
The :argument_id in each comment is a reference to the :id in the affirmatives/negatives
And here's the code that generates the modals.
(rf/reg-event-db
:app/show-modal
(fn [db [_ modal-id]]
(assoc-in db [:app/active-modals modal-id] true)))
(rf/reg-event-db
:app/hide-modal
(fn [db [_ modal-id]]
(update db :app/active-modals dissoc modal-id)))
(rf/reg-sub
:app/active-modals
(fn [db _]
(:app/active-modals db {})))
(rf/reg-sub
:app/modal-showing?
:<- [:app/active-modals]
(fn [modals [_ modal-id]]
(get modals modal-id false)))
(defn modal-card [id title body footer]
[:div.modal
{:class (when #(rf/subscribe [:app/modal-showing? id]) "is-active")}
[:div.modal-background
{:on-click #(rf/dispatch [:app/hide-modal id])}]
[:div.modal-card
[:header.modal-card-head
[:p.modal-card-title title]
[:button.delete
{:on-click #(rf/dispatch [:app/hide-modal id])}]]
[:section.modal-card-body
body]
[:footer.modal-card-foot
footer]]])
(defn modal-button [id title body footer]
[:div
[:button.button.is-primary
{:on-click #(rf/dispatch [:app/show-modal id])}
title]
[modal-card id title body footer]])
All your modals are passed the same id :argument. Thus, you always show the same modal, since the db lookup in your subscription is for :argument.
A minor tip: Following kebap-case is the recommended style in Clojure. So your app-db attributes should probably be argument-id and topic-id.
Furthermore, I would recommend subscribing debate-affirmatives and debate-negatives inside the components you pass them to. This would make them more self-containing.

How to set array as state in Reagent using ClojureScript

I'm trying to create a state called items however my code gives an error and I'm unsure of why when I try to access the items and iterate over them. What am I doing wrong here?
(def items (r/atom ["test" "test2"]))
(defn home-page []
[:div#main
[:section.section
[:h1#s-one-greeting "Hello, I'm testing"]
[:h2#s-one-greeting-two "blah blah blah"]]
[:section.section
[:p "Work history"]
[:p "yada"]
[:p "yada"]
[:ul
(for [item items]
^{:key item} [:li "item " item])]]])
You are accessing items without derefing it. So you are trying to for loop over the reagent atom which doesn't work. Just switch it to (for [item #items] ...) and you should be fine.

Animate antizer table with rc-animate in re-frame app

I am trying to recreate the example in http://react-component.github.io/table/examples/animation.html to add animation to a table in a re-frame app. The table is rendered using antizer which is a ClojureScript library for Ant Design react components. For the animation I'm trying to use rc-animate (as in the example) which is a JavaScript library.
To integrate rc-animate, I followed the official Webpack guide and created a src/js/index.js file:
import Animate from 'rc-animate';
window.Animate = Animate;
My project.clj is:
(defproject ant-table-animation "0.1.0-SNAPSHOT"
:dependencies [[org.clojure/clojure "1.8.0"]
[org.clojure/clojurescript "1.10.238"]
[reagent "0.8.1"]
[re-frame "0.10.5" :exclusions [reagent]]
[antizer "0.3.1"]]
:plugins [[lein-cljsbuild "1.1.7"]]
:min-lein-version "2.5.3"
:source-paths ["src/clj" "src/cljs"]
:clean-targets ^{:protect false} ["resources/public/js/compiled" "target"]
:figwheel {:css-dirs ["resources/public/css"]}
:profiles
{:dev
{:dependencies [[binaryage/devtools "0.9.10"]
[cider/piggieback "0.3.9"]
[figwheel-sidecar "0.5.16"]
[day8.re-frame/re-frame-10x "0.3.3"]]
:plugins [[lein-figwheel "0.5.16"]]}
:prod { }
}
:cljsbuild
{:builds
[{:id "dev"
:source-paths ["src/cljs"]
:figwheel {:on-jsload "ant-table-animation.core/mount-root"}
:compiler {:closure-defines {re-frame.trace.trace_enabled_QMARK_ true}
:main ant-table-animation.core
:output-to "resources/public/js/compiled/app.js"
:output-dir "resources/public/js/compiled/out"
:asset-path "js/compiled/out"
:source-map-timestamp true
:preloads [devtools.preload, day8.re-frame-10x.preload]
:external-config {:devtools/config {:features-to-install :all}}
:infer-externs true
:npm-deps false
:foreign-libs [{:file "dist/index_bundle.js"
:provides ["rc-animate" "rc-animate-child"]
:global-exports {rc-animate Animate
rc-animate-child AnimateChild}}]
}}
{:id "min"
:source-paths ["src/cljs"]
:compiler {:main ant-table-animation.core
:output-to "resources/public/js/compiled/app.js"
:optimizations :advanced
:closure-defines {goog.DEBUG false}
:pretty-print false}}
]}
)
and in my views.cljs I try to render the table like this:
(ns ant-table-animation.views
(:require
[re-frame.core :as re-frame]
[ant-table-animation.subs :as subs]
[ant-table-animation.events :as events]
[antizer.reagent :as ant]
[reagent.core :as reagent]
[rc-animate]
))
(.log js/console rc-animate)
(defn AnimateBody
[props]
(.createElement
js/React
rc-animate
(.assign js/Object #js {:transitionName "move", :component "tbody"} props)))
(.log js/console AnimateBody)
(defn orders
[]
(let [orders #(re-frame/subscribe [::subs/orders])
width 80]
[ant/table
{:columns
[{:title "Product Name" :dataIndex :product :width width}
{:title "Quantity" :dataIndex :quantity :width width}
{:title "Unit Price" :dataIndex :price :width width}
{:title "Actions" :dataIndex "actions" :width width
:render
#(reagent/as-element
[ant/button
{:icon "delete" :type "danger"
:on-click
(fn []
(re-frame/dispatch [::events/order-deleted (.-product %2)]))}])}]
:dataSource orders
:size "small"
:components {:body {:wrapper AnimateBody}}
:pagination {:page-size 20}
:scroll {:y 300}}]))
(defn main-panel []
(let [name (re-frame/subscribe [::subs/name])]
[:div
[:h1 "Hello from " #name]
[orders]
]))
I am not sure at all about the
(defn AnimateBody
[props]
(.createElement
js/React
rc-animate
(.assign js/Object #js {:transitionName "move", :component "tbody"} props)))
being equivalent to the line from the example
const AnimateBody = props => <Animate transitionName="move" component="tbody" {...props} />;
The table renders correctly, but when I try to delete a row it fails with the following error trace:
react-dom.development.js:55 Uncaught Error: Unable to find node on an unmounted component.
at invariant (react-dom.development.js:55)
at findCurrentFiberUsingSlowPath (react-dom.development.js:4256)
at findCurrentHostFiber (react-dom.development.js:4266)
at findHostInstance (react-dom.development.js:17676)
at Object.findDOMNode (react-dom.development.js:18145)
at AnimateChild.transition (AnimateChild.js:83)
at AnimateChild.componentWillLeave (AnimateChild.js:70)
at performLeave (Animate.js:339)
at Array.forEach (<anonymous>)
at Animate.componentDidUpdate (Animate.js:188)
at commitLifeCycles (react-dom.inc.js:15386)
at commitAllLifeCycles (react-dom.inc.js:16646)
at HTMLUnknownElement.callCallback (react-dom.inc.js:143)
at Object.invokeGuardedCallbackDev (react-dom.inc.js:193)
at invokeGuardedCallback (react-dom.inc.js:250)
at commitRoot (react-dom.inc.js:16800)
at completeRoot (react-dom.inc.js:18192)
at performWorkOnRoot (react-dom.inc.js:18120)
at performWork (react-dom.inc.js:18024)
at performSyncWork (react-dom.inc.js:17996)
at requestWork (react-dom.inc.js:17884)
at scheduleWork (react-dom.inc.js:17689)
at Object.enqueueForceUpdate (react-dom.inc.js:11855)
at Object.Component.forceUpdate (react.inc.js:479)
at reagent$impl$batching$run_queue (batching.cljs?rel=1541330682770:39)
at Object.flush_queues (batching.cljs?rel=1541330682770:86)
at Object.run_queues (batching.cljs?rel=1541330682770:76)
at batching.cljs?rel=1541330682770:63
at re_frame_10x.cljs?rel=1541164419576:125
It is also indicated that:
The above error occurred in the <Animate> component:
in Animate (created by ant_table_animation.views.animateBody)
in ant_table_animation.views.animateBody (created by BaseTable)
in table (created by BaseTable)
in BaseTable (created by Connect(BaseTable))
in Connect(BaseTable) (created by BodyTable)
in div (created by BodyTable)
in BodyTable (created by ExpandableTable)
in div (created by ExpandableTable)
in div (created by ExpandableTable)
in div (created by ExpandableTable)
in ExpandableTable (created by Connect(ExpandableTable))
in Connect(ExpandableTable) (created by Table)
in Provider (created by Table)
in Table (created by LocaleReceiver)
in LocaleReceiver (created by Table)
in div (created by Spin)
in AnimateChild (created by Animate)
in div (created by Animate)
in Animate (created by Spin)
in Spin (created by Table)
in div (created by Table)
in Table (created by ant_table_animation.views.orders)
in ant_table_animation.views.orders (created by ant_table_animation.views.main_panel)
in div (created by ant_table_animation.views.main_panel)
in ant_table_animation.views.main_panel
I am a Clojure beginner, and I know even less for React; this is where I ended up after a week of trying but now I feel stuck. I have uploaded my project in github for anyone that would like to give it a try.
The Unable to find node on an unmounted component error was occurring because of version issues with React. I was able to handle it by explicitly using the version of React used by the rc-animate library - 16.5.2 in my project.clj:
...
[reagent "0.8.1" :exclusions [cljsjs/react cljsjs/react-dom [cljsjs/react-dom-server]]]
[cljsjs/react "16.5.2-0"]
[cljsjs/react-dom "16.5.2-0"]
[cljsjs/react-dom-server "16.5.2-0"]
...
[antizer "0.3.1" :exclusions [cljsjs/react cljsjs/react-dom [cljsjs/react-dom-server]]]]
...
For the correct definition of the AnimateBody component I had to use a combination of reagent/adapt-react-class, reagent/as-element and reagent/reactify-component.
Specifically, in my views.cljs I define the component as:
(def animate (reagent/adapt-react-class rc-animate))
(def animateBody
(fn [props]
(reagent/as-element [animate (assoc props :transition-name "move" :component "tbody")])))
and then pass it to the ant/table component with:
...
:components {:body {:wrapper (reagent/reactify-component animateBody)}}
...

Passing a Cell to a defelem Hoplon

Say I had the following custom textarea, how can I pass the etextarea a Cell and have both the :change and the :value work?
(defelem etextarea
[temp-cell]
(textarea :value temp-cell
:change #(reset! temp-cell #%)))
The code can be modified to include '#temp-cell' or whatever but I have tried many things and whether I pass it '(cell= cell-to-pass)' or 'cell-to-pass' it doesn't seem to work.
(defelem etextarea
[_ [temp-cell]]
(textarea :value temp-cell
:change (fn [evt]
(reset! temp-cell #evt))))

Clojurescript react-leaflet does not display tiles properly

I am trying to use react-leaflet from ClojureScript but I have problem with the way tiles render:
some tiles do not display
there are tile showing next to each other in different cities
Here is the code I have:
(ns carder-devcards.map
(:require [taoensso.timbre :as timbre]
[cljsjs.react-leaflet] ;; js/ReactLeaflet
)
(:require-macros [devcards.core :as dc :refer [defcard]]))
(defn build
([component props]
(build component props (array)))
([component props & children]
(.createElement js/React
component
(clj->js props)
(array children))))
(def tile-layer (partial build js/ReactLeaflet.TileLayer))
(def leaflet-map (partial build js/ReactLeaflet.Map))
(def marker (partial build js/ReactLeaflet.Marker))
(def popup (partial build js/ReactLeaflet.Popup))
(defcard simple-leaflet
(fn [state]
(let [{:keys [pos zoom] :as st} #state
tl (tile-layer {:url "http://{s}.tile.osm.org/{z}/{x}/{y}.png"
:attribution "© OpenStreetMap contributors"})
mk (marker {:position pos})]
(leaflet-map {:center pos :zoom zoom}
tl
mk
)))
{:pos [51.505, -0.09]
:zoom 13}
{:header true})
And here is the result I have locally.
Note: resizing the browser seems to have an effect, so this could be a css problem as well (?). I have tried including the following without effect:
.leaflet-container {
height: 400px;
width: 100%;
}
The answer from #Chris Murphy set me up on the right track since I had the same error with it.
It turns out I was missing the css files of leaflet.js, including them resolved my problem.
Just posting my own very simple first cut leaflet on the off chance that it helps:
(def URL-OSM "http://{s}.tile.osm.org/{z}/{x}/{y}.png")
(defn create-map []
(let [m (-> js/L
(.map "mapid2")
(.setView (array -33.8675 151.2070) 9)) ;; Sidney
tile1 (-> js/L (.tileLayer URL-OSM
#js{:maxZoom 16
:attribution "OOGIS RL, OpenStreetMap ©"}))
base (clj->js {"OpenStreetMap" tile1})
ctrl (-> js/L (.control.layers base nil))]
(.addTo tile1 m)
(.addTo ctrl m)))
I am using [cljsjs/leaflet "0.7.7-4"].
Edit
And here's a leaflet-centric version of the markup:
(hiccup/html
[:head
[:meta {:charset "UTF-8"}]
[:meta {:name "viewport" :content "width=device-width, initial-scale=1"}]
[:link {:rel "stylesheet" :href "http://cdn.leafletjs.com/leaflet/v0.7.7/leaflet.css" :type "text/css"}]
[:script {:src "http://cdn.leafletjs.com/leaflet/v0.7.7/leaflet.js" :charset "utf-8"}]
[:body
[:div {:id "mapid"}]
[:div {:id "main-app-area"}]
[:script {:src "/reconnect/js/main.js" :type "text/javascript"}]])