How to set array as state in Reagent using ClojureScript - 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.

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.

reset! on reagent atom doesn't work as intended

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)]]]])))

ClojureScript - Ajax - Retrieving json

I have my dependencies
(ns test.core
(:require [reagent.core :as reagent :refer [atom]]
[ajax.core :refer [GET]]))
I then have my handler that handles the responses to my ajax call
(defn handle-response [response]
(println (type response))
(println film))
THe data from my call is JSON, in the browser it looks like this:
{"id":3,"name":"Sicario","star":"Emily Blunt","rating":5}
When I run the above Clojure, I see this:
cljs.core/PersistentArrayMap core.cljs:192:23
{id 3, name Sicario, star Emily Blunt, rating 5}
Is their a way for me to go from the PersistArrayMap, and destructure it into id, name, star and rating?
I tried
(let [film (js->clj (.parse js/JSON response) :keywordize-keys true)]
...)
Hoping I would get a map named film, but no avail!
I think maybe I could use (get-in), but can't get the syntax right for this either.
Thanks,
Got it,
I was missing :response-format :json and :keywords? true from my GET call
Now, it looks like this:
(GET (str "https://localhost:5001/api/films/" film-name)
{:response-format :json
:keywords? true
And it all works.

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"}]])

How to get the selflink in a compojure handler?

When defining a compojure handler e.g. by using the defroutes macro, I can do something like this:
(defroutes home-routes
(GET "/myhome/:id" [ id ] (home-page)))
(defn home-page [ id ]
( ... do something ... ))
So I know how to pass a piece of the path parameter. But imagine I want to return a HAL+JSON object with a selflink. How would I get defroutes to pass the whole URI to the home-page function?
The Ring request map contains all the necessary information to construct a "selflink". Specifically, :scheme, :server-name, :server-port, and :uri values can be assembled into full request URL. When I faced this problem I created Ring middleware that adds the assembled request URL to the Ring request map. I could then use the request URL in my handlers as long as I pass the request map (or some subset of it) into the handler. The following snippet shows one way of implementing this:
(defroutes app-routes
(GET "/myhome/:id" [id :as {:keys [self-link]}] (home-page id self-link))
(route/resources "/")
(route/not-found "Not Found"))
(defn wrap-request-add-self-link [handler]
(fn add-self-link [{:keys [scheme server-name server-port uri] :as r}]
(let [link (str (name scheme) "://" server-name ":" server-port uri)]
(handler (assoc r :self-link link)))))
(def app
(-> app-routes
handler/site
wrap-request-add-self-link))