### Custom logger with set-loggers! Source: https://github.com/day8/re-frame/blob/master/docs/api-re-frame.core-instrumented.md Example of defining a custom logging function and installing it via `set-loggers!` with a map containing the `:warn` and `:log` keys. ```clojure (defn my-logger ;; my alternative logging function [& args] (post-it-somewhere (apply str args))) ;; now install my alternative loggers (re-frame.core/set-loggers! {:warn my-logger :log my-logger}) ``` -------------------------------- ### Setup event handlers with dispatch-sync Source: https://github.com/day8/re-frame/blob/master/docs/Testing.md Use this setup approach when you want to test event handlers by dispatching events that build up the app-db state. It uses dispatch-sync instead of dispatch because dispatch is asynchronous and would not handle the event immediately. This approach is not pure because it mutates the global app-db, but it is pragmatic and the setup is natural. ```clojure ;; setup - cummulatively build up db (dispatch-sync [:initialise-db]) (dispatch-sync [:clear-panel]) (dispatch-sync [:draw-triangle 1 2 3]) ;; execute (dispatch-sync [:select-triange :other :stuff]) ;; validate that the value in 'app-db' is correct ;; perhaps with subscriptions ``` -------------------------------- ### inject-cofx usage examples Source: https://github.com/day8/re-frame/blob/master/docs/Coeffects.md Shows three example calls to inject-cofx with different cofx-ids and optional additional values. ```clojure (inject-cofx :random-int 10) ``` ```clojure (inject-cofx :guid) ``` ```clojure (inject-cofx :now) ``` -------------------------------- ### Manual db setup for testing select-triangle Source: https://github.com/day8/re-frame/blob/master/docs/Testing.md Shows a manual setup approach for testing an event handler by constructing a literal db map and event vector. This approach is fragile because it ties tests to the internal structure of app-db and requires manual construction. ```clojure ;; a test (let [ ;; setup - create db and event db {:some 42 :thing "hello"} ; a literal event [:select-triangle :other :event :args] ;; execute result-db (select-triange db event)] ;; validate that result-db is correct) (is ...) ``` -------------------------------- ### More event vector examples Source: https://github.com/day8/re-frame/blob/master/docs/dominoes-live.md Additional example events. The second shows a namespaced keyword for non-trivial applications. ```clojure [:admit-to-being-satoshi false] [:dressing/put-pants-on "velour flares" {:method :left-leg-first :belt false}] ``` -------------------------------- ### console usage examples Source: https://github.com/day8/re-frame/blob/master/docs/api-re-frame.core-instrumented.md Example usage of the `console` function with `:error` and `:warn` levels. ```clojure (console :error "Sure enough it happened:" a-var "and" another) (console :warn "Possible breach of containment wall at:" dt) ``` -------------------------------- ### on-changes usage example Source: https://github.com/day8/re-frame/blob/master/docs/api-re-frame.core-instrumented.md Example of using on-changes: defines a function my-f, creates an interceptor watching paths [:a] and [:b] and writing to [:c], then uses it in a reg-event-db handler. ```clojure (defn my-f [a-val b-val] ... some computation on a and b in here) ;; use it (def my-interceptor (on-changes my-f [:c] [:a] [:b])) (reg-event-db :event-id [... my-interceptor ...] ;; <-- ultimately used here (fn [db v] ...)) ``` -------------------------------- ### SQL query with parameters Source: https://github.com/day8/re-frame/blob/master/docs/dominoes-live.md Traditional SQL query example used to illustrate the concept of querying with parameters, analogous to re-frame's subscribe with query parameters. ```sql select * from customers where name="blah" ``` -------------------------------- ### Call run to start the application Source: https://github.com/day8/re-frame/blob/master/docs/dominoes-live.md Calls the run function to kick off the application after the HTML page loads. After this, the app passively waits for events. ```clojure (run) ``` -------------------------------- ### View using the subscription Source: https://github.com/day8/re-frame/blob/master/docs/Flows.md Defines a view that subscribes to the flow's output and renders it. Also includes the root rendering setup. ```clojure (defn app-container [& children] (into [:div {:style {:padding "1rem" :border "2px solid grey"}}] children)) (defn room-calculator [room] [:div [room-form room] " Area:" @(rf/subscribe [:area room])]) ;; <--- subscribing (rf/dispatch-sync [:init]) (defonce garage-calculator-root (rdc/create-root (js/document.getElementById "garage-calculator"))) (rdc/render garage-calculator-root [app-container [room-calculator :garage]]) ``` -------------------------------- ### Configure shadow-cljs alias to replace core with alpha Source: https://github.com/day8/re-frame/blob/master/docs/FAQs/alpha.md Example configuration for shadow-cljs to alias re-frame.core to re-frame.alpha, enabling a drop-in replacement. ```clojure {... :builds {:my-app {... :build-options {:ns-aliases {re-frame.core re-frame.alpha}}}}} ``` -------------------------------- ### Auto-compile tests with bb watch-test Source: https://github.com/day8/re-frame/blob/master/CONTRIBUTING.md Starts an auto-compiling test runner. After running, open http://localhost:3449/ in a browser for an auto-reloaded browser-based test runner. ```sh bb watch-test ``` -------------------------------- ### Define run function to initialize and mount the app Source: https://github.com/day8/re-frame/blob/master/docs/dominoes-live.md Defines the app root, mounts the UI, and starts the application by dispatching the :initialize event synchronously and mounting the UI. The use of dispatch-sync ensures app-db has a correct structure before any subscriptions or event handlers run. ```clojure (defonce dominoes-live-app-root (rdc/create-root (js/document.getElementById "dominoes-live-app"))) (defn mount-ui [] (rdc/render dominoes-live-app-root [ui])) ;; mount the application's ui (defn run [] (rf/dispatch-sync [:initialize]) ;; puts a value into application state (mount-ui)) ``` -------------------------------- ### Side-effecting handler with HTTP GET Source: https://github.com/day8/re-frame/blob/master/docs/EffectfulHandlers.md Example of a re-frame event handler that performs an HTTP GET side effect. Although it works, the side effect makes testing harder and breaks event replay-ability. ```clojure (reg-event-db :my-event (fn [db [_ a]] (GET "http://json.my-endpoint.com/blah" ;; dirty great big side-effect {:handler #(dispatch [:process-response %1]) :error-handler #(dispatch [:bad-response %1])}) (assoc db :flag true))) ``` -------------------------------- ### reg-event-db handler accessing localStorage directly Source: https://github.com/day8/re-frame/blob/master/docs/Coeffects.md Event handler registered with reg-event-db that directly accesses localStorage to get a value, making the handler impure. This is the motivating example that the tutorial aims to refactor. ```clojure (reg-event-db :load-defaults (fn [db _] (let [val (js->clj (.getItem js/localStorage "defaults-key"))] ;; <-- Problem (assoc db :defaults val)))) ``` -------------------------------- ### Clone and enter the re-frame repository Source: https://github.com/day8/re-frame/blob/master/docs/README.md Clone the re-frame repository and navigate into it to set up the local development environment. ```sh git clone https://github.com/day8/re-frame.git cd re-frame ``` -------------------------------- ### Display entire app-db with pprint Source: https://github.com/day8/re-frame/blob/master/docs/FAQs/Inspecting-app-db.md Display the entire app-db in the view using pprint. Combines the short and long answers. ```clojure [:pre (with-out-str (pprint @re-frame.db/app-db))] ;; see everything! ``` -------------------------------- ### Thread Last Macro ->> Practice Example Source: https://github.com/day8/re-frame/blob/master/docs/clojurescript.md Practice example for the thread-last macro ->>. Evaluates to a sequence of odd numbers from 1 to 9. ```clojure (->> (range 10) ; (range 10) (map inc) ; (map inc (range 10)) (filter odd?)) ; (filter odd? (map inc (range 10)) ``` -------------------------------- ### Generate API documentation with ns-to-markdown Source: https://github.com/day8/re-frame/blob/master/docs/README.md Build the API documentation by running the ns-to-markdown Clojure command from the docs directory, outputting to api-re-frame.core.md. ```sh cd docs/ clojure -m ns-to-markdown ../src/re_frame/core.cljc > api-re-frame.core.md ``` -------------------------------- ### Event vector example Source: https://github.com/day8/re-frame/blob/master/docs/dominoes-live.md Example of a re-frame event vector. The first element is a keyword identifying the event kind, and further elements provide additional data. ```clojure [:time-color-change "red"] ``` -------------------------------- ### Cancel interval effect data format Source: https://github.com/day8/re-frame/blob/master/docs/FAQs/PollADatabaseEvery60.md Data format for cancelling a previously started interval. The :id must match the one used in :start. ```clojure {:interval {:action :cancel :id :panel-1-query}} ;; the id provided to :start ``` -------------------------------- ### Basic initial data loading pattern with re-frame Source: https://github.com/day8/re-frame/blob/master/docs/Loading-Initial-Data.md Shows the basic pattern for loading initial data: register subscriptions for :name and :initialised?, an event to initialise the db, and components that render based on subscription state. The main function dispatches the initialise event and renders the top-panel. ```Clojure (re-frame.core/reg-sub ;; supplied main-panel with data :name ;; usage (subscribe [:name]) (fn [db _] (:display-name db))) (re-frame.core/reg-sub ;; we can check if there is data :initialised? ;; usage (subscribe [:initialised?]) (fn [db _] (not (empty? db)))) ;; do we have data (re-frame.core/reg-event-db :initialise-db (fn [db _] (assoc db :display-name "Jane Doe"))) (defn main-panel ;; the top level of our app [] (let [name (re-frame.core/subscribe [:name])] ;; we need there to be good data [:div "Hello " @name]))) (defn top-panel ;; this is new [] (let [ready? (re-frame.core/subscribe [:initialised?])] (if-not @ready? ;; do we have good data? [:div "Initialising ..."] ;; tell them we are working on it [main-panel]))) (defn ^:export main ;; call this to bootstrap your app [] (re-frame.core/dispatch [:initialise-db]) (reagent.dom/render [top-panel] (js/document.getElementById "app"))) ``` -------------------------------- ### Kick start Reagent with main-panel and main Source: https://github.com/day8/re-frame/blob/master/docs/Loading-Initial-Data.md Defines a top-level reagent component and a main function that renders it into the DOM. Calling main bootstraps the app and triggers rendering of the full component tree. ```clojure (defn main-panel ;; my top level reagent component [] [:div "Hello DDATWD"]) (defn ^:export main ;; call this to bootstrap your app [] (reagent.dom/render [main-panel] (js/document.getElementById "app"))) ``` -------------------------------- ### Basic item list with add/delete controls Source: https://github.com/day8/re-frame/blob/master/docs/Flows.md A stripped-down todomvc example showing how to add and remove items in a list. It defines subscriptions, events, and UI components, then renders them into the DOM element with id "item-counter-basic". ```clojure (rf/reg-sub :items :-> (comp reverse :items)) (rf/reg-event-db ::add-item (fn [db [_ id]] (update db :items conj id))) (rf/reg-event-db ::delete-item (fn [db [_ id]] (update db :items #(remove #{id} %)))) (defn item [id] [:div "Item" id]) (defn items [] (into [:div] (map item) @(rf/subscribe [:items]))) (defn controls [] (let [id (or (apply max @(rf/subscribe [:items])) 0)] [:div [:span {:style clickable :on-click #(rf/dispatch [::add-item (inc id)])} "Add"] " " [:span {:style clickable :on-click #(rf/dispatch [::delete-item id])} "Delete"] " "])) (defonce item-counter-basic-root (rdc/create-root (js/document.getElementById "item-counter-basic"))) (rdc/render item-counter-basic-root [app-container [controls] [items]]) ``` -------------------------------- ### Start interval effect data format Source: https://github.com/day8/re-frame/blob/master/docs/FAQs/PollADatabaseEvery60.md Data format for starting a regular dispatch. The :id is used to cancel later, :frequency is in milliseconds, and :event is the event to dispatch. ```clojure {:interval {:action :start :id :panel-1-query ;; my id for this (so I can cancel later) :frequency 60000 ;; how many ms between dispatches :event [:panel-query 1]}} ;; what to dispatch ``` -------------------------------- ### Basic let form Source: https://github.com/day8/re-frame/blob/master/docs/clojurescript.md Shows the structure of a `let` form: a vector of binding pairs followed by a body. This example binds `a` to "pen" and returns `a`, evaluating to "pen". ```clojure ; a let form has two parts: ; - 1st a vector of "binding pairs" ; - 2nd a "body" (let [a "pen"] ; a pair - binds the symbol `a` to the value "pen" a) ; the body is evaluated with the bindings ``` -------------------------------- ### Version 1: Event handler with GET side effect using reg-event-db Source: https://github.com/day8/re-frame/blob/master/docs/Talking-To-Servers.md Registers a :request-it event handler that initiates an HTTP GET via cljs-ajax and updates a loading flag in app-db. The GET callbacks dispatch further events (:process-response and :bad-response) to avoid closing over stale db. This handler is impure due to the side effect. ```clojure (ns my.app.events ;; <1> (:require [ajax.core :refer [GET]] [re-frame.core :refer [reg-event-db])) (reg-event-db ;; <-- register an event handler :request-it ;; <-- the event id (fn ;; <-- the handler function [db _] ;; kick off the GET, making sure to supply a callback for success and failure (GET "http://json.my-endpoint.com/blah" {:handler #(dispatch [:process-response %1]) ;; <2> further dispatch !! :error-handler #(dispatch [:bad-response %1])}) ;; <2> further dispatch !! ;; update a flag in `app-db` ... presumably to cause a "Loading..." UI (assoc db :loading? true))) ;; <3> return an updated db ``` -------------------------------- ### Thread Last Macro ->> Example Source: https://github.com/day8/re-frame/blob/master/docs/clojurescript.md Shows the nested form example and its equivalent using the thread-last macro ->>. The macro threads the result of each form as the last argument to the next form. ```clojure (map count (map str (map inc [9 99 999]))) ``` ```clojure (->> [9 99 999] (map inc) ; (map inc [9 99 999]) (map str) ; (map str (map inc [9 99 999])) (map count)) ; (map count (map str (map inc [9 99 999]))) ``` -------------------------------- ### Set up timer with defonce Source: https://github.com/day8/re-frame/blob/master/docs/dominoes-live.md Sets up a timer that calls dispatch-timer-event every second. defonce prevents a new timer from being created on hot reload, which is important in a dev environment. ```clojure (defonce do-timer (js/setInterval dispatch-timer-event 1000)) ``` -------------------------------- ### Run tests with bb watch Source: https://github.com/day8/re-frame/blob/master/CONTRIBUTING.md Builds and runs the tests in one step. Requires npm, babashka, and chromium. ```sh bb watch ``` -------------------------------- ### Use unwrap interceptor with dispatch and handler Source: https://github.com/day8/re-frame/blob/master/docs/api-re-frame.core-instrumented.md Shows how the unwrap interceptor promotes the payload map to the event handler. The dispatch example pairs with the handler example, which uses unwrap in the interceptor vector and destructures the payload directly. The original event is available under :original-event in coeffects. ```clojure (dispatch [:event-id {:x 1 :y 2 :z 3}]) ``` ```clojure (reg-event-fx :event-id [... unwrap ...] ;; <-- added to the interceptors (fn [{:keys [db]} {:keys [x y z]}] ;; <-- instead of [_ {:keys [x y z]}] ...) ``` -------------------------------- ### Alternative :initialize handler using db Source: https://github.com/day8/re-frame/blob/master/docs/dominoes-live.md Alternative version of the :initialize handler that uses the existing db value (initially {}) and assocs the new keys onto it, instead of ignoring db. ```clojure (rf/reg-event-db :initialize (fn [db _] ;; we use db this time, so name it (-> db ;; db is initially just {} (assoc :time (js/Date.)) (assoc :time-color "orange"))) ``` -------------------------------- ### reg-sub-raw Source: https://github.com/day8/re-frame/blob/master/docs/flow-mechanics.md Documentation for the `reg-sub-raw` registration function, including its usage, parameters, and an example. ```APIDOC ## reg-sub-raw ### Description Registers a subscription handler at a low level, similar to `reg-sub`, but the handler receives the `app-db` as a reagent/atom and must return a `reaction` (or a value created with `reagent/make-reaction`). ### Method Registration function ### Endpoint `(re-frame.core/reg-sub-raw :query-id some-fn)` ### Parameters #### Arguments - **query-id** (keyword) - Required - The subscription id, later used with `(subscribe [:query-id])`. - **some-fn** (function) - Required - A function of two arguments: `app-db` (a reagent/atom) and `query-v` (the query vector). It must return a `reaction` that computes the subscription value. ### Request Example ```clj (re-frame.core/reg-sub-raw :query-id (fn [app-db query-v] (reaction (get-in @app-db [:some :path])))) ``` ### Response #### Success Response No explicit return value; the subscription is registered for later use with `subscribe`. #### Response Example N/A ### Notes - `app-db` is a reagent/atom, not a value. - The returned `reaction` must dereference `app-db` (and any `subscribe` calls) to establish input signals. - There is no 3-arity version; input signals are obtained by calling `subscribe` inside the `reaction`. - You can use `reagent/make-reaction` instead of the `reaction` macro to attach an `:on-dispose` handler for cleanup. ``` -------------------------------- ### Simple App File Structure Source: https://github.com/day8/re-frame/blob/master/docs/App-Structure.md Recommended file structure for simpler apps, separating each layer into its own file. Note that events.cljs and subs.cljs are not required by other namespaces, so they must be explicitly required in core.cljs to ensure registrations occur. ```text src ├── core.cljs <--- entry point, plus history, routing, etc ├── db.cljs <--- schema, validation, etc (data layer) ├── views.cljs <--- reagent views (view layer) ├── events.cljs <--- event handlers (control/update layer) └── subs.cljs <--- subscription handlers (query layer) ``` -------------------------------- ### Reagent reaction and ratom signal flow example Source: https://github.com/day8/re-frame/blob/master/docs/flow-mechanics.md Shows how Reagent's `reaction` macro wraps computations that dereference `ratoms`, creating a signal graph where changes to `app-db` automatically re-run dependent reactions. The example prints the initial values, resets `app-db`, and prints the updated values to demonstrate the flow. ```Clojure (ns example1 (:require-macros [reagent.ratom :refer [reaction]]) ;; reaction is a macro (:require [reagent.core :as reagent])) (def app-db (reagent/atom {:a 1})) ;; our root ratom (signal) (def ratom2 (reaction {:b (:a @app-db)})) ;; reaction wraps a computation, returns a signal (def ratom3 (reaction (condp = (:b @ratom2) ;; reaction wraps another computation 0 "World" 1 "Hello"))) ;; Notice that both computations above involve de-referencing a ratom: ;; - app-db in one case ;; - ratom2 in the other ;; Notice that both reactions above return a ratom. ;; Those returned ratoms hold the (time varying) value of the computations. (println @ratom2) ;; ==> {:b 1} ;; a computed result, involving @app-db (println @ratom3) ;; ==> "Hello" ;; a computed result, involving @ratom2 (reset! app-db {:a 0}) ;; this change to app-db, triggers re-computation ;; of ratom2 ;; which, in turn, causes a re-computation of ratom3 (println @ratom2) ;; ==> {:b 0} ;; ratom2 is result of {:b (:a @app-db)} (println @ratom3) ;; ==> "World" ;; ratom3 is automatically updated too. ``` -------------------------------- ### Effects map with :db and :fx instructions Source: https://github.com/day8/re-frame/blob/master/docs/Effects.md Illustrates the structure of an effects map. The :db key instructs to reset app-db to the supplied value, and the :fx key instructs an ordered list of other effects, here a :dispatch. ```clojure {:db (assoc db :flag a) ;; side effect on app-db :fx [[:dispatch [:do-something-else 3]]]} ;; dispatch this event ``` -------------------------------- ### with-frame Source: https://github.com/day8/re-frame/blob/master/docs/EPs/002-ReframeInstances.md Macro that dynamically binds a frame for the body, useful for tests, REPL, and setup. ```APIDOC ## with-frame ### Description Macro that dynamically binds a frame for the body, useful for tests, REPL, and setup. ### Signature `(rf/with-frame frame & body)` ### Parameters - **frame** (frame) - Required - The frame to bind. - **body** (forms) - Required - The body to execute with the frame bound. ### Returns - Result of the body. ### Example ```clojure (rf/with-frame left-frame (rf/dispatch [:counter/inc])) ``` ``` -------------------------------- ### Define add function Source: https://github.com/day8/re-frame/blob/master/docs/clojurescript.md Defines a two-argument function that adds its arguments, used with map in the following examples. ```clojure (defn add [a b] (+ a b)) ``` -------------------------------- ### Run MkDocs Material container (Linux) Source: https://github.com/day8/re-frame/blob/master/docs/README.md Run the MkDocs Material container with hot reloading on Linux. ```sh docker run --rm -it -p 8000:8000 -v ${PWD}:/docs squidfunk/mkdocs-material:5.1.1 ``` -------------------------------- ### Define plus-one function Source: https://github.com/day8/re-frame/blob/master/docs/clojurescript.md Defines a function that adds one to its argument, used with map in the following examples. ```clojure (defn plus-one [it] (+ 1 it)) ``` -------------------------------- ### Use my-reg-event-db to register an event Source: https://github.com/day8/re-frame/blob/master/docs/FAQs/GlobalInterceptors.md Example usage of the custom my-reg-event-db replacement, which automatically adds omni-ceptor to the interceptor chain. ```clojure (my-reg-event-db :event-id (fn [db v] ...)) ```