clojure-demos/reagent-reitit-demo/resources/public/documents/reagent-reitit.org

4.7 KiB

Minimal clojurescript reagent example

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.

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.

(defn navbar-link [{:keys [href title text] :or {text nil title nil}}]
  [:a.link.dim.dib.mr3 {:key href :href href :title title} text])

[navbar-link {:href "https://clojure.org" :title "linke title" :text "link here"}]
(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"}]]

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.

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

[my-component "Clickable component" 1]
(defn update-form-data [form-data name value]
  (swap! form-data assoc [name] value))

(defn my-address-form []
  (let [form-data (r/atom {})]
    (fn [] 
      [:form
       [:input {:name "address-line-1" :on-change  :placeholder "Address Line 1"}]
       [:input {:name "address-line-2" :placeholder "Address Line 2"}]
       [:input {:name "address-line-3" :placeholder "Address Line 3"}]
       [:input {:name "city" :placeholder "City"}]
       [:input {:name "postcode" :placeholder "Postcode / Zipcode"}]
       ]))

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.

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

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.

(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 "]