Image Won't Update with Swap - clojure

The following code displays 5 different emojis at the bottom of the screen, and 1 emoji in the upper center. I am trying to make it so that when one of the emojis on the bottom is clicked, that same emoji appears at the top center. I am able to update the atom that contains the emoji history with :on-click, but the image does not update with the current url.
(def emoji-history
(atom {:current "img/sad-tears.png"}))
(defn Img40 [src reaction]
[:img {:src src
:style {:width "60px"
:padding-right "20px"}
:on-click #(do
(js/console.log (get #emoji-history :current))
(swap! emoji-history assoc :current src)
(js/console.log (get #emoji-history :current)))}])
(defn CurrentEmoji []
[:img {:style {:width 40 :margin-top 15}
:src (get #emoji-history :current)}])
(defn EmojiDisplay []
[:div {:style {:text-align "center"}}
[CurrentEmoji]
[:div {:style {:text-align "center"
:margin-top "200px"
:padding-left "20px"}}
[Img40 "img/smile.png" "happy"]
[Img40 "img/sad-tears.png" "sad"]
[Img40 "img/happy-tears.png" "amused"]
[Img40 "img/surprised.png" "surprised"]
[Img40 "img/angry.png" "angry"]]])

Refer to the reagent.core namespace and use a Reagent atom like this:
(ns my-name.space.etc
(:require [reagent.core :as r]))
(def emoji-history
(r/atom {:current "img/sad-tears.png"}))
The line of code that you have here...
(swap! emoji-history assoc :current src)
...where you swap! your atom's value, that is correct :-)
Unlike a plain old Clojure atom, when a Reagent atom's value (state) is changed, a re-render of the UI is triggered.
Rarely is the entire UI re-rendered, though. Because Reagent wraps React, the React system will work out the minimal required changes to the DOM, so it's pretty efficient.

Related

How in Reagent with Hiccup make an element with takes all available space

I am trying to find out how in Reagent with Hiccup make an element with takes all available space. So an resize parent I will get :component-did-mount call back.
(defn chart [id col-width row-height]
(let [dimensions (atom {})]
(reagent/create-class
{:component-did-mount
(fn [e]
(let [thisComponent (aget (js/document.querySelector ".app") "parentNode")
width (aget thisComponent "offsetWidth")
height (aget thisComponent "offsetHeight")]
(swap! dimensions {:width width :height height})
(println "----did mountwdth" width "--" height col-width row-height)
(.log js/console thisComponent)))
:reagent-render
(fn [id col-width row-height]
[:div
[:div {:style {:background "gray"}} "--drag handle--"]
[:div.non-dragable
[simple-bar id]
[tchart id col-width (int (- row-height controls-height))]]])})))
I want the chart element to take all the space available.
React lifecycle callbacks like ComponentDidMount does not react to component size changes.
If you want to fire a callback whenever the component size changes - you'll need to use some third-party React libraries like react-measure or react-sizeme
The other strategy is to add an event listener on window resize and get your component's parent size from there.
I use React Virtualized's AutoSizer for this. Example of integration with Reagent:
(ns example
(:require
[cljsjs.react]
[cljsjs.react-virtualized]
[goog.object :as gobject]
[reagent.core :as r]))
(defn autosizer-example
[]
(r/with-let [width (r/atom 500)
_ (js/setTimeout #(reset! width 1000)
1000)]
[:div {:style {:width (str #width "px")}}
[:> js/ReactVirtualized.AutoSizer
{:disableHeight true
:disableWidth true}
(fn [props]
(let [width (gobject/get props "width")]
(r/as-element
[:div
"Width of parent: " width])))]]))
Docs: https://github.com/bvaughn/react-virtualized/blob/master/docs/AutoSizer.md

How to get files from <input type='file' …/> in ClojureScript

I'm trying to get image file in input field but I could not do it. Here is the code:
:on-change (fn [_]
(this-as this
(println "Files: " (.-files this))))
But (.-files this) returns nil.
Any ideas?
P.S: I would like to upload this image to my server.
Here is a workable snippet from our project:
:on-change
(fn [this]
(if (not (= "" (-> this .-target .-value)))
(let [^js/File file (-> this .-target .-files (aget 0))]
;; your logic here ...
;; now reset the widget to let user upload a new one
(set! (-> this .-target .-value) ""))))
Hope this would help.
To expand on Albert's answer (and because my edits were rejected):
Instead of using goog.object/get as below (and in his answer with o/get):
(let [dom (goog.object/get event "target")
file (goog.object/getValueByKeys dom #js ["files" 0])]))
You can also use regular interop forms in ClojureScript:
:on-change (fn [event]
(let [files (.. event -target -files) ; returns JS Array
file (first files)]
(do-something-with file)]))
you must first get the dom node from somewhere. In React world this might not be the dom since they are syntactic events.
:on-change (fn [event]
(let [dom (goog.object/get event "target")
file (goog.object/getValueByKeys dom #js ["files" 0])]))
Or using regular interop forms in ClojureScript:
:on-change (fn [event]
(let [files (.. event -target -files) ; returns JS Array
file (first files)]
(do-something-with file)]))
https://reactjs.org/docs/handling-events.html

Correctly manipulating state in reagent

I just learn Reagent in Clojurescript, I am just following some tutorial but maybe I miss something I have this code for the state
(defonce app-state (atom {:text "Hello Chestnut!" :click-count 0}))
and the rendered view
(defn article []
[:div
[:div "The atom" [:code "click-count"] " has value : " (:click-count #app-state)]
[:input {:type "button" :value "Add"
:on-click #(swap! (:click-count #app-state) inc)}]
]
)
I'm trying to increment the state when they button is pressed, but I got this error on the console
Error: No protocol method ISwap.-swap! defined for type number: 0
the atom should be swapped not the :click-count
(swap! app-state update :click-count inc)

Detect Click Coordinates Clojure

I have the following ClojureScript code and am trying to detect the coordinates of a a click. So far, I can't even get Javascript alerts to recognize a click, let alone give me the coords.
I know I will have to write a function to have it give me the exact cells being clicked, but as a start, need to know how to get the coordinates of any area clicked on a page.
Thanks!
(defn header [color text]
[:h1
{:style
{:color color
:background-color "blue"}}
text])
(defn Cell []
[:div
{:style
{:width "40px"
:height "40px"
:float "right"
:margin-bottom "1px"
:margin-right "1px"
:background-color "grey"
:border "1px" "solid" "white"}}])
(defn home-page []
[:div
[header "red" "Minesweeper"]
[:div
{:style
{:width "440px"
:height "440px"}}
(repeat 100 [Cell])]])
Put an :onClick key at the same map indentation level as the :style. Its value should be a function, which will get an event e. Then:
(let [coords (.getClientPosition e)
coords' {:x (aget coords "x")
:y (aget coords "y")}])
Here's an example of a hashmap that has an :onClick event and its function:
{ :href "#"
:onClick (fn [e]
(.preventDefault e)
(swap! counter inc))}
The only thing that matters in the above is getting the e and using it. That was taken from the flappybird example program, which is how I got started.

clojurescript + reagent issue

I am working on a simple web-app using clojurescript and reagent. I would like to create a simple "tab" component, which will contain (for starters) a text-input component.
The app has 2 tabs and the user has the option to choose a tab and I want to "preserve" the values in each of these two tabs.
Here's the code:
(defn atom-input [value]
[:input {:type "text"
:value #value
:on-change #(reset! value (-> % .-target .-value))}])
(defn simple-tab [index]
(let [pg-index (atom 1)
a (atom 0)]
(fn []
[:div
[:h4 (str "index: " #index)]
[atom-input a]])))
(defn main-page []
(let [index (atom 0)]
[:div.container
[:div.row
[:button {:on-click (fn [] (reset! index 0))} "select tab 1"]
[:button {:on-click (fn [] (reset! index 1))} "select tab 2"]]
[:div.row
[simple-tab index]]]))
(defn ^:export run []
(reagent/render-component
(fn [] [main-page])
(.-body js/document)))
The problem is that when I switch the tab, the components share the values of the input field - what am I please doing wrong here?
Thank you so much for your help!
The problem is you're passing a (atom 0) to the atom-input control: [atom-input a].
This caused the same atom value to be shared between your tabs.
If you don't want to share the value, you'll need change a to a map: a (atom {}) and pass the map and the index to atom-input, e.g.:
(defn atom-input [value index]
[:input {:type "text"
:value (or (get #value index) "")
:on-change #(swap! value assoc index (-> % .-target .-value))}])
(defn simple-tab [index]
(let [pg-index (atom 1)
a (atom {})]
(fn []
[:div
[:h4 (str "index: " #index)]
[atom-input a #index]])))
A better approach, IMHO, is to use cursor so you don't need to pass the index & the whole map to atom-input, e.g.:
(defn atom-input [value]
[:input {:type "text"
:value (or #value "")
:on-change #(reset! value (-> % .-target .-value))}])
(defn simple-tab [index]
(let [pg-index (atom 1)
a (atom {})]
(fn []
[:div
[:h4 (str "index: " #index)]
[atom-input (reagent/cursor [#index] a)]])))
I think there are a couple of problems here because you are mixing up application data (state) and display logic data (i.e. DOM). If you keep the two things distinct i.e. maintain your application state in one atom and data relating to component display in another, then things may be a bit cleaner.
Your simple-tab component does not need to know anything about the tab state. It just needs to know about the app state i.e. the value entered/stored via atom-input. Therefore, rather than passing it index, pass it the atom you want it to use. this would require some higher level logic to determine the call. for example, if you had a number of tabs, you might have something like
(condp = #index
0 [simple-tab tab0-atom]
1 [simple-tab tab1-atom]
...
n [simple-tab tabn-atom])
or you could modify simple-tab so that the value passed in i.e. index value is used as a key into the app-state - a cursor would be easiest I think i.e.
(def app-state (r/atom {:tabs {0 nil 1 nil}}})
(defn simple-tab [index]
(let [val-cur (r/cursor app-state [:tabs index])]
[atom-input val-cur]))
You are using a form-2 component, that is, a component that returns a function.
More details here : https://github.com/Day8/re-frame/wiki/Creating-Reagent-Components#form-2--a-function-returning-a-function
When doing so, only the returned function is called so your atom-inputs are sharing the same atom.
Also, you should use the same argument in the inner function
(defn simple-tab [index]
(let [pg-index (atom 1)
a (atom {})]
(fn [index]
[:div
[:h4 (str "index: " #index)]
[atom-input a #index]])))
In your case, you are passing an atom so this is not important but you could have error in the future if you forgot this.
Concerning the broader architecture, I would advise you to use a single global atom. Try to have as much as possible state in this atom and avoid component local state so this is easier to reason about.
You could also name your tabs like :product :users and use a multimethod to render the correct tab based on the selected one. This is easier to read and easier to add new tabs in the future.