Skip to content

Configuration

zalky edited this page Apr 27, 2023 · 4 revisions

Reflet configuration can be outlined as such:

  1. Minimum Config
  2. Options
  3. Descriptions
  4. Development and Production Builds
  5. Debugger
  6. React Peer Dependency

Minimum Config

A fully functioning system, minus the debugger, can be configured in a single event:

(require '[reflet.core :as f])

(f/disp-sync [::f/config])

At minimum this event provides the Reflet graph db with a set of unique attributes (see the References and Application Design and the Multi Model DB documents to see what unique attributes are and how they are used).

This event must be processed before any graph db operations occur, or any graph pull subscriptions are created. A typical hot-reloadable development setup might look like:

(ns my.client
  (:require [my.client.ui :as ui]
            [re-frame.core :as f*]
            [reagent.dom :as dom]
            [reflet.core :as f]))

(defn render!
  "Called on each hot reload."
  []
  (f*/clear-subscription-cache!)
  (some->> "container"
           (.getElementById js/document)
           (dom/render [ui/app])))

(defn init!
  "Called only once on application start."
  []
  (f/disp-sync [::f/config])          ; <- config only happens once, must be synchronous!
  (render!))                          ; <- then the application runs

Options

If you need something beyond the defaults, you can pass a config map to the Reflet's ::f/config event:

(f/disp-sync [::f/config
              {:id-attrs         #{:kr.domain/uuid :system/uuid :view/uuid}
               :dispatch         [:some-other-config-event]
               :trace-queue-size 100}])

The currently available options are:

  • :id-attrs: [optional] A set of unique id attributes that configure the graph db. A default set is provided by reflet.db/default-unique-attributes. If the debugger is enabled, this default set will always be included to support the debugger implementation, in addition to any you define using this option.

  • :dispatch: [optional] Dispatches an asynchronous event immediately after configuration

  • :pull-fn: [optional] A function that replaces the default pull implementation. This can be a bit involved, in that you need to take the time to understand the input and output contract of reflet.db/default-pull-impl, as explained in that function's doc string. But as long as you fulfill this contract you can swap out the entire query parsing engine, and everything about Reflet should continue to work: from the graph db, to the event-sourced, reactive pull queries, to the FSMs, and even the debugger. For example, you could drop in a query engine for GraphQL, or modify the query DSL already implemented by Reflet.

  • :trace-queue-size: [optional] Sets the number of traces captured by the debugger panels, default is 50

  • :debug-hotkey: [optional] Sets the debugger hotkey character, which when combined with Ctrl, toggles the purple debugger with-ref marks. The default is \j, which means you would press Ctrl + J to toggle the debugger overlay.

If you need, you can also define your own configuration handler in place of ::f/config, however at minimum it must configure the db with a set of unique attributes:

(require '[reflet.core :as f]
         '[reflet.db :as db])

(f/reg-event-db ::my-config
  ;; Configured with a custom set of unique attributes
  (fn [db _]
    (db/new-db db #{:kr.domain/uuid :system/uuid :view/uuid :js/uuid}))

SLF4J Warnings

This is not specific to Reflet, but depending on the version of Re-frame you are using, you might see these SLF4J warnings.

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.

This is due to Re-frame's transient dependency on org.clojure/tools.logging. They are harmless, but if you'd like to suppress them all you have to do is explicitly include a SLF4J binding in your dependencies, for example:

{org.slf4j/slf4j-nop {:mvn/version "2.0.6"}}

Descriptions

Reflet's polymorphic descriptions should work with the minimum config. However, if you dispatch an additional configuration event, :reflet.core/config-desc, you get some extra features:

  1. Polymorphic type hierarchies
  2. Prefers tables
  3. Customizable type attributes (if you're using the descriptions feature you'll almost certainly want to override Reflet's default :kr/type type attribute)
  4. Removal of stale definitions when hot-reloading code

See the sections on Configuration and Hot-Reloading in the Polymorphic Descriptions document for more details on where and how to dispatch this event.

Development and Production Builds

The example client extends the code seen above to generate two builds:

  1. Development build: contains Reflet debugger and Re-frame-10x
  2. Production build: no debugger or Re-frame-10x

The relevant files would be:

Debugger

The debugger is not included in the application target by default. To use it, there are two steps:

  1. Include the reflet.debug.preload namespace in your development build configuration.

  2. Call the debugger render method to mount it to the DOM.

Preload Namespace

If you're using shadow-cljs you can load the preload namespace just like Reflet's example shadow-cljs.edn:

{:builds {:dev {...
                :devtools {...
                           :preloads [reflet.debug.preload]} ; <- here!
                ...}}}

If you're using lein-cljsbuild, you might do something like:

{:profiles 
 {:dev
  {:cljsbuild
   {:builds
    [{:id           "dev"
      :source-paths ["src" "dev"]
      :compiler     {...
                     :preloads [reflet.debug.preload]
                     :main     "my.client"}}]}}}}

Or with figwheel-main:

{:main     "my.client"
 :preloads [reflet.debug.preload]
 ...**

Ultimately, each build framework has its own way of including preload namespaces. Read their respective documentations to learn how.

Render the Debugger

Besides the preload, you also have to mount the debugger onto the DOM.

Important: this needs to happen after any call to (re-frame.core/clear-subscription-cache!).

So the hot-reloadable setup above could be modified like so:

(ns my.client
  (:require [my.client.ui :as ui]
            [re-frame.core :as f*]
            [reagent.dom :as dom]
            [reflet.core :as f]
            [reflet.debug.ui :as debug]))

(defn render!
  "Called on each hot reload."
  []
  (f*/clear-subscription-cache!)
  (debug/render!)                            ; Call after sub cache is cleared
  (some->> "container"
           (.getElementById js/document)
           (dom/render [ui/app])))

(defn init!
  "Called only once on application start."
  []
  (f/disp-sync [::f/config])          ; <- config only happens once, must be synchronous!
  (render!))                          ; <- then the application runs

React Peer Dependency

Like Re-frame itself, Reflet considers React a peer dependency, which means you need to make sure it is available. If you are already using Re-frame in your project you've probably already done this.

Otherwise, there are generally two approaches.

Either via npm and shadow-cljs:

npm install react@17.0.2 react-dom@17.0.2

Or by adding CLJSJS React packages to your project:

[cljsjs/react "17.0.2-0"]
[cljsjs/react-dom "17.0.2-0"]

React 18

Everything about Reflet is compatible with React 18. Unrelated to Reflet, there are a couple of upstream workarounds that you may need to use to get the new createRoot approach to work with hot reloading. You can include the following revision of Reflet in your deps.edn to try it out:

{:deps {io.zalky/reflet {:git/url "https://github.com/zalky/reflet.git"
                         :git/sha "b9298ec636b523f117ee127e842acb23f6a0bdbc"}}

Note that the dev build might print some warnings that reagent.dom/dom-node is deprecated when compiling the CLJS target, and an also a warning that ReactDOM.render is no longer supported in React 18. in the browser console. This is from the re-frame-10x debugger. If you remove it from the build, everything should work without any warnings.


Next: References and Application Design

Home: Home