Tuck is a micro framework for building Reagent apps that have a clean separation of view code and event processing code. View components in tuck are just pure functions without any magic.
Tuck defines a protocol for events and how they are processed and provides a simple way to send events from UI code to be processed.
Tuck can be used at any level (not just at the app root): simply pass tuck a reagent atom and a component.
Tuck is heavily inspired by Petrol but is even lighter and has no dependencies (aside form Reagent itself).
See the codox generated API documentation.
The entrypoint to tuck is the tuck.core/tuck
reagent component which takes your app state (a ratom) and your
root component as arguments. Tuck will create a control handle and call your root component with the it and the current
dereferenced value of the app state. The control handle is used to dispatch events for processing and is typically
called e!
. All changes to app state are done by event processing and must be sent through the control handle.
This is a minimal example that shows how to use tuck. Normally you would want to separate your view code and your event definitions to separate name spaces.
(ns tuck-example.core
(:require [reagent.core :as r]
[tuck.core :as t :refer-macros [define-event]]))
(define-event UpdateName [new-name]
{:path [:name]}
new-name)
(defn my-root-component [e! {:keys [name :as app]]
[:input {:value name
:on-change #(e! (->UpdateName (-> % .-target .-value)))}])
(def app-state (r/atom {}))
(defn main []
(r/render [t/tuck app-state my-root-component]
(.getElementById js/document "app")))
Events are anything that implement the tuck.core/Event
protocol. The protocol defines a single
method called process-event
that takes the event and the current value of the app state and
produces a new app state.
You can define your events as records and define the implementation separately with
extend-protocol
or use the convenience macro define-event
which defines the event record and the
processing code. The define-event
macro has an option called :path
which is a vector defining a
path in the app state where the update should take place. If no path is defined, the root app state
is passed.
Sometimes events just need to assoc a value somewhere in the app state, there is a further
convenience macro called define-assoc-events
which takes alternating names and app state paths.
An application will most likely need some asynchronous events to communicate with servers or set
timeouts. This can be done by using tuck.core/send-async!
which must be called within event
processing code. The send-async!
function takes an event constructor and optional arguments
and returns a callback that will create and and apply the event when called.
You can use send-async!
to create callbacks to pass to XHR calls.
;; Simple async example
(define-event FetchThings
{}
;; Launch an XHR call and set events events as callbacks
(GET! "/fetch-things" {:on-success (t/send-async! ->FetchThingsResponse)
:on-failure (t/send-async! ->ServerError)})
;; Return new app state
(assoc app :fetching-things? true))
define-assoc-events
convenience macrodefine-event
macro for defining the record and implementation in one goMinor fix release
Support clojure.spec validation of app state. Add options map as 3rd argument that can specify new options:
Clone this repo and run "lein figwheel dev" in the examples folder.
Can you improve this documentation?Edit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close