ReAgent

Reagent binds components to data using an atom. An atom is a ClojureScript reference type. An atom references an immutable value, but the reference itself is mutable, it can be changed using reset! and swap! to reference a different imumtable value. Changes to atom’s are atomic.

The value can be retrived by dereferencing the atom using deref or the @ prefix.

Functions can be attached to atoms to watch for changes, the function will get the atoms old and new state as arguments.

Reagent provides its own atom, reagent/atom, which has watches setup to rerender compontents which reference the atom.

(defonce app-state (reagent/atom {})) ; define a var, app-state, which is an reagent/atom which references an empty hash-map
 
(defn my-compontent [] [:div "Hello"]) ; define a var, my-compontent, which is a function returning Hiccup syntax HTML

What’s more we can bind functions to Javascript events such as click.

(defn my-compontent [] [:div { :on-click (fn [x] (println x) }  "Hello"])

When the “Hello” div is clicked a message will be printed to the browser console.

Using the above we could bind functions to events and have them perform actions which update atoms, which will in turn re-render compontents.

  • An atom references an immutable value
  • An atom is dereferenced with @my-atom.
  • The immutable value the atom references can be changed with reset! and swap!
  • Functions can be attached to an atom to watch for changes made by reset! and swap!.

An atom therefore tracks state over time. This opens up possibilities of things like undo.

ReFrame

The use of atoms are the backbone of Reframe.

ReFrame encorages the use of a single reagent/atom to store the entire app state.

(def app-state (reagent/atom {}))

We will use a reagent atom and initialize it as an empty hash. We can then store arbitrary data in it.

You are encoraged to think of app-state as an in-memory database. Having all the characteristics of memory, ephemeral. You can partition the data within however you like. For example each compontent might have its own key in the hash the atom references.

Why keep all data in one place?

  • Single source of truth
  • Components can grab state from one place, or grabs other components state if they are coupled. No need to sync compontent state between distinct storage locations.
  • You can send the entire client side state to the server to persist.
  • Debugging is easier because you snapshot a particular instance in time

Reaction

A reaction is function which has an regaent/atom as input and returns an reagent/atom.

The reaction is automatically run (and re-run) when the input atom is changed, which in turn will change (reset!) the output atom.

Internally ReFrame knows when the input atom changes due to assigned watchers, it reruns the function passing in the changed atom and mutates the output atom using reset!.

Therefore the atoms represent state over time, they are an active stream.

ReFrame wraps Reagent compontents in a Reaction. This means two things, 1. your compontent always return a reagent/atom and 2. any changes to the reagent/atom’s used by the compontent cause a re-render and an update of the returned atom.

Any changes to the state flow through the compontent, which derives data (Hiccup) from the state, the VDOM is then dervied from Hiccup and the DOM dervied from the VDOM.

Instead of a static compontent we have a pipeline, a stream. Less in’s and out’s more of an active pipeline. It reacts to changes. This is starting to feel like FRP (Functional Reactive Programming).

atom(state) -> compontent(function) -> atom(hiccup) -> DOM

Change the state and the DOM is updated automatically. The DOM is a function of the state.

Luckily we don’t have to hook all of this up, that is what ReFrame does for us.

Subscribers

Our components will proberbly need to query our application state, but they should only really be concerned with rendering, not how the state looks, or how it might need to be manipulated for presentation.

To do this compontents can subscribe to ????

??? need to query the state and return a reaction.

(defn my-compoent []
  (let [name (subscribe [:name-query])]
    (fn [] 
      [:div "Hello " @name])))

In order to use a subscription our compontent must return a function which does the job of rendering.

The outer function, my-component, sets up the subscription. The inner anoymous function returns Hiccup HTML.

The outer function is only ever called once, the inner function will be called every time the compontent needs to be rendered.

The subscription has one argument, a vector, where the first element is the name of the subscription.

Now we need to write a handler function with the same name as the subscription which gets data from the app state. Think of them as creating a view of the data. They can extract raw data from the state or process it in some way, for example only returning data matching some criteria.

(register-sub :name-query
  (fn [state _]
    (reaction :name @state)))

If state changes, the query is re-run and in turn the component is run and the DOM updated.

This covers data flowing from state changes to the DOM, but what about browser events effecting the state?

Events

Compontents also need to dispatch events, for example when clicked. Those events need handling. In ReFrame dispatching an event and handling it are done by different functions.

(defn my-compontent []
  [:div "Hello" { :on-click #(dispatch [:my-compontent-clicked]) }])

The #(...) syntax is sugar for an anoymous function, #(...) is the same as (fn [] ...).

The on-click will dispatch, i.e. broadcast, an event called :my-compontent-clicked. This event is represented as a vector where the first element is the event name, followed by any number of additional parameters.

If the compontent represented an item in a collection we might also include the item id in the event

Now we need an event handler. The event handle is responsible for updating the state.

(defn my-compontent-clicked [state _]
  ...)

The event handler must return a value which used to update the state via reset!. Since the state changes any compontents which reference the state are rendered.

The event handler must be registered.

(register-handler :my-compontent-clicked my-component-click)

Event handlers effectivly fold events in to the current state, much like reduce.

So state is derived from events and the DOM is derived from the state.

events -> event handler(function) -> atom(state) -> compontent(function) -> atom(hiccup) -> DOM

Event handlers are not just for user based interactions like click but also HTTP request failure/success.