re-frame/docs/coeffects.md

5.5 KiB

Introduction

This tutorial explains coeffects.

It explains what they are, how they are created, how they help, and how to manage them in tests. There's an adults-only moment.

Table Of Contexts

Coeffects

What Are They?

coeffects are the input resources required by an event handler to perform its computation. By "resources" I mean "data".

Because the majority of event handlers require only db and event, there's a registration function called reg-event-db which delivers these two inputs as arguments to an event handler, making it easy.

But sometimes an event handler needs other inputs to perform their computation. Things like a random number, or a GUID, or the current datetime. Perhaps it might need access to an in-memory DataScript database.

An Example

This handler is obtaining data directly from LocalStore

(reg-event-fx
   :load-localstore
   (fn [coeffect _]
     (let [val (js->clj (.getItem js/localStorage "defaults-key"))]  ;; <-- Problem
       (assoc db :defaults val))))

Because this event handler has directly accessed a resources, it has stopped being pure, causing well documented problems to to accrue.

Let's Fix It Up

Here's how we'd like to write it. Data should only come from the coeffects argument:

(reg-event-fx                     ;; using -fx registration
   :load-localstore
   (fn [coeffects event]         ;; 1st argument is coeffects
     (let [val (:local-store coeffects)
           db   (:db coeffects)]  ;; <-- get value from argument
       {:db (assoc db :defaults val))})) ;; returns an effect

Problem solved?

Well, no. We must still arrange for the right LocalStore value to be put
into coeffects.

How Are Coeffect Babies Made?

Awkward moment, but it is now time for THAT talk. Err, hurm. When two coeffects love each other very much ... no, stop ... it doesn't happen that way. This is a G rated framework. Instead ...

Every time an event handler is executed, a new context is created, and within that context is a new :coeffect map, which is initially totally empty.

That pristine context value is then threaded through a chain of Interceptors, and sitting on the end of chain is the handler, wrapped up as the final interceptor. We know
this story well from a previous tutorials.

So it becomes obvious that Interceptors put coeffects in place. Or more specifically, the :before functions of Interceptors.

So, Next Step

So now we now tweak our localstore-consuming event handler in one way:

(reg-event-fx                     
   :load-localstore
   [ (coeffect :local-store "defaults-key") ]     ;; <-- this is new
   (fn [coeffects event]         
     (let [val (:local-store coeffects)
           db  (:db coeffects)]  
       {:db (assoc db :defaults val))})) 

Problem solved?

Well, no, not yet. What's this coeffects function returning and how does it know to load LocalStore.

coeffect the function

coeffect is a part of re-frame.

It returns an Interceptor whos :before fucntion loads a value into a context's :coeffect.

It takes either one or two arguments. The first is always the id of the coeffect required (called a cofx-id). The 2nd is optional addition value.

So in the case above, the cofx-id was :local-store and the additional calue was "defaults-key".

Other Uses of coeffects

Here's a couple of other examples:

  • (coeffects :random-int 10)
  • (coeffects :guid)

In a previous tutorial we also learned that reg-events-db and reg-events-fx add Interceptors to front of any chain during registration. They insert an Interceptor called do-effects, which ultimately actions effects.

And it turns out that they also add Interceptors which cause the value of app-db to be placed in coeffects. They do it by adding this to any events Interceptor chain:

(coeffects :db)

coeffects is a function, which takes one parameter, and that's the "coeffects handler" which will summon the right value. The function returns an Interceptor.

Okay, let me explain that a bit more. I've gone a bit fast.

Notes:

  1. (coeffects {:something value}) or {coeffects :something) will look up a cofx handler for :something
  2. An Interceptor is returned from the
  3. It will call this handler passing in value.
  4. It will take the returned value and add it to the :coffect of the context under the key.

So it will come as no surprise to you to learn that it is Interceptors which fill :coeffects with "necessary inputs".

The :coeffect might start pristine and empty, but it is threaded through the :before functions of each Interceptor in the chain prior to the handler interceptor sitting at the end.

But which Interceptors?

We saw in a previous tutorial that both . At the time, also add another interceptor and it looks like this:

(coeffects :db)

So that interceptor is added to every one of your registered event handlers. And it adds :db to the coeffect.

So how does (coffects :db)` work? How could I create my own.

From previous tutorials we are familiar with reg-event-db and reg-event-fx add their own Interceptors to any chain you provide. Well, one of the interceptors XXX

XXXX

specifically a map of data. the inputs required by an event handler.

At a more pragmatic level, coeffects is a map in context

How

An Interceptor factory called coeffect

reg-cofx

Examples

XXX need a random number???

Testing

It immedaitely becomes harder to test, etc.