#+TITLE: ClojureScript reagent example's

* Introduction
Reagent is a popular react wrapper in the clojurescript it greatly simplify build react SPA Applications this is usually a good starting point when learning, but there are lots of other options that are worth considering.

#+BEGIN_SRC html :results silent :exports none :tangle resources/public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>Clojure demos</title>
    <link rel="stylesheet" type="text/css" href="https://unpkg.com/tachyons@4.12.0/css/tachyons.min.css">
</head>
<body>
    <div id="app">
     App loading here
    </div>
    <script src="/cljs-out/main_bundle.js" type="application/javascript"></script>
</body>
</html>
#+END_SRC

#+BEGIN_SRC edn :results silent :exports none :tangle deps.edn
{:paths ["src" "resources"]
 :deps
 {org.clojure/clojure {:mvn/version "1.10.0"}
  org.clojure/clojurescript {:mvn/version "1.11.60"}
  reagent/reagent {:mvn/version "1.2.0"}
  thheller/shadow-cljs {:mvn/version "2.24.0"}}}
#+END_SRC

#+BEGIN_SRC edn :results silent :exports none :tangle shadow-cljs.edn
{:deps {:aliases [:dev]}
 :dev-http {8080 ["resources/public/" "classpath:public"]}
 :source ["src" "../../components"]
 :builds {:app {:output-dir "resources/public/cljs-out/"
                :asset-path "/cljs-out"
                :target :browser
                :compiler-options {:infer-externs :auto
                                   :externs ["datascript/externs.js"]
                                   :output-feature-set :es6}
                :modules {:main_bundle {:init-fn clojure-demo.core/startup!}}
                :devtools {:after-load app.main/reload!}}}}
#+END_SRC

#+BEGIN_SRC json :results silent :exports none :tangle package.json
{
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  },
  "devDependencies": {
    "shadow-cljs": "^2.23.3",
    "webpack": "^5.74.0",
    "webpack-cli": "^4.10.0"
  }
}
#+END_SRC

#+BEGIN_SRC text :results silent :exports none :tangle readme.org
#+TITLE: Getting started

#+END_SRC

* Install the npm requirements.

npx install

* Launch shadow-cljs watch for source code changes
#+BEGIN_SRC sh
npx shadow-cljs watch app
#+END_SRC


#+BEGIN_SRC json :tangle :exports none src/clojure_demo/core.cljs
(ns clojure-demo.core
  (:require
   ["react-dom/client" :refer [createRoot]]
   [reagent.core :as reagent]))
#+END_SRC


* Components
The basis of any react app is components these are small snippets of html which can be composed to build up a page, 
reagent provides 3 main ways to create components dependant on the complexity needed most of the time you will create form 1 and form 2 components.

** Form 1 components
Form one components are the most basic simply rendering some html with values that are not going to change.

In the example below the function just returns some hiccup with the parameters inserted, you need to specify =:key= when dynamically repeating the elements and these should be reproducible unique id's where possible not randomly generated or indexed numbers if the data is unordered.


*** Example navbar component
#+BEGIN_SRC clojure :tangle src/clojure_demo/core.cljs
(defn navbar-link [{:keys [href title text] :or {text nil title nil}}]
  [:a.link.dim.dib.mr3.mb2.dark-blue {:key href :href href :title title} text])

(defn navbar []
  [:div
   [navbar-link {:href "https://clojure.org" :title "Clojure site" :text "Clojure"}]
   [navbar-link {:href "https://github.com/reagent-project/reagent" :title "Reagent" :text "Reagent"}]
   [navbar-link {:href "https://github.com/metosin/reitit" :title "Reitit" :text "Reitit"}]])

[navbar]
#+END_SRC

*** Example product cards
#+BEGIN_SRC clojurescript :results output :tangle src/clojure_demo/core.cljs
(defn product-card
  [{:keys [title amount description link]}]
  [:article.br2.ba.dark-gray.b--black-10.ma2.w-100.w-50-m.w-25-l.mw5
   [:img.db.w-100.br2.br--top {:src link}]
   [:div.pa2.ph3-ns.pb3-ns
    [:div.dt.w-100.mt1
     [:div.dtc [:h1.f5.f4-ns.mv0 title]]
     [:div.dtc.tr [:h2.f5.mv0 amount]]]
    [:p.f6.lh-copy.measure.mt2.mid-gray description]]])

[:div.flex
 [product-card
      {:title "Cat 01"
       :amount "£54.59"
       :description "Cat 1 description here"
       :link "http://placekitten.com/g/600/300"}]
 [product-card
      {:title "Cat 02"
       :amount "£34.59"
       :description "Cat 2 description here"
       :link "http://placekitten.com/g/600/300"}]]
#+END_SRC

** Form 2 components
Form two components are used so we can track local state of a component, this is appropriate any time we need to react to change forms and user click event's being simple examples.

*** Example Click counter component
#+BEGIN_SRC clojurescript :results output :tangle src/clojure_demo/core.cljs
(ns example
  (:require
    [reagent.core :as r]
    [reagent.dom.server :as rdom]))

(defn my-component [title starting-value]
  (let [local-state (reagent/atom starting-value)]
    (fn [title]
      [:h1 {:class (when @local-state "hide")
            :on-click (fn [e]
                        (prn (-> e .-target))
                        (swap! local-state inc))}
       (str title " " @local-state)])))

[my-component "Clickable component" 1]
#+END_SRC

*** Example address capture component
#+BEGIN_SRC clojurescript :results output :tangle src/clojure_demo/core.cljs
(defn update-form-data [form-data ^js event]
  (swap! form-data assoc
         (keyword (-> event .-target .-name))
         (-> event .-target .-value)))

(defn my-address-form []
  (let [form-data (reagent/atom {})
        form-change (partial update-form-data form-data)]
    (fn []
      [:div
       (str @form-data)
       [:form {:on-submit prn}
        [:input.db.ma2.pa2 {:type "text"
                    :default-value (str (:test @form-data))
                    :name "address-line-1"
                    :on-change form-change :placeholder "Address Line 1"}]
        [:input.db.ma2.pa2 {:type "text" :name "address-line-2" :on-change form-change :placeholder "Address Line 2"}]
        [:input.db.ma2.pa2 {:name "address-line-3"  :on-change form-change :placeholder "Address Line 3"}]
        [:input.db.ma2.pa2 {:name "city" :on-change form-change :placeholder "City"}]
        [:input.db.ma2.pa2 {:name "postcode" :on-change form-change :placeholder "Postcode / Zipcode"}]]])))

[:div [my-address-form]]
#+END_SRC


** Form 3 components
This form of component give's you full access to the react life cycle methods, so render did-mount did-unmount etc
usually this form of component is only needed when rendering graphics or things like graphs, it's also useful for capturing errors and handling them as in the example below, which renders your components but if =component-did-catch= is trigger the error is caught and displayed instead.

*** Error boundary example
If you hit an error react will stop rendering and remove the user interface, you can use an error boundary to capture this so part of the UI can still render.
#+BEGIN_SRC clojurescript :results output :tangle src/clojure_demo/core.cljs
(defn err-boundary
  [& children]
  (let [err-state (reagent/atom nil)]
    (reagent/create-class
      {:display-name "ErrBoundary"
       :component-did-catch (fn [err info]
                              (reset! err-state [err info]))
       :reagent-render (fn [& children]
                         (if (nil? @err-state)
                           (into [:<>] children)
                           (let [[_ info] @err-state]
                             [:pre [:code (pr-str info)]])))})))

[err-boundary [:div ""]]
#+END_SRC

*** Example of using the google maps library
https://developers.google.com/maps/documentation/javascript/load-maps-js-api#dynamic-library-import
#+BEGIN_SRC clojurescript :results output :tangle src/clojure_demo/core.cljs
(defn load-google-maps-script [api-key]
  (let [script (.createElement js/document "script")]
    ;; copied from googles recommended way of loading google maps
    (set! (.-innerHTML script) (str "(g=>{var h,a,k,p=\"The Google Maps JavaScript API\",c=\"google\",l=\"importLibrary\",q=\"__ib__\",m=document,b=window;b=b[c]||(b[c]={});var d=b.maps||(b.maps={}),r=new Set,e=new URLSearchParams,u=()=>h||(h=new Promise(async(f,n)=>{await (a=m.createElement(\"script\"));e.set(\"libraries\",[...r]+\"\");for(k in g)e.set(k.replace(/[A-Z]/g,t=>\"_\"+t[0].toLowerCase()),g[k]);e.set(\"callback\",c+\".maps.\"+q);a.src=`https://maps.${c}apis.com/maps/api/js?`+e;d[q]=f;a.onerror=()=>h=n(Error(p+\" could not load.\"));a.nonce=m.querySelector(\"script[nonce]\")?.nonce||\"\";m.head.append(a)}));d[l]?console.warn(p+\" only loads once. Ignoring:\",g):d[l]=(f,...n)=>r.add(f)&&u().then(()=>d[l](f,...n))})({
key: \"" api-key "\", v: \"weekly\"});"))
    (.appendChild (.-head js/document) script)))
#+END_SRC


* Fetching a html element reference
If we wish to capture a node we can use =:ref= and store the result in an atom, we can then de reference the atom and call a method on the node using =aget=.

#+BEGIN_SRC clojurescript :results output :tangle src/clojure_demo/core.cljs
(defn example-ref-component [title]
  (let [local-ref (reagent/atom nil)]
    (fn [title]
      [:div#example-ref-id.flex.items-center.justify-center.pa4.bg-lightest-blue.navy
       {:ref #(reset! local-ref %)}
       (str title (when @local-ref (aget @local-ref "id")))])))

[example-ref-component "Grabbing the element id using ref "]
#+END_SRC


#+BEGIN_SRC clojure :exports none :tangle src/clojure_demo/core.cljs
(defn current-page []
 [:div
  [navbar]
  [:div.flex
   [product-card
    {:title "Cat 01"
     :amount "£54.59"
     :description "Cat 1 description here"
     :link "http://placekitten.com/g/600/300"}]
   [product-card
    {:title "Cat 02"
     :amount "£34.59"
     :description "Cat 2 description here"
     :link "http://placekitten.com/g/600/300"}]]
  [my-component "Clickable component" 1]
  [:div [my-address-form]]])

(defn mount-root-page []
 ;; this select the main node from the html file and injects your page content
 (.render
  (createRoot (.getElementById js/document "app"))
  (reagent/as-element [err-boundary [current-page]])))

(def startup! (mount-root-page))

#+END_SRC


* Further reading

https://github.com/reagent-project/reagent

https://purelyfunctional.tv/guide/reagent/#what-is-reagent

https://github.com/metosin/reitit

https://www.metosin.fi/blog/reitit/
