Liking cljdoc? Tell your friends :D

logo

Fulcro is a library for building data-driven full-stack applications for the web. It uses React and is written in Clojure and Clojurescript.

NOTE: If you are using Java 9, you may need to include "--add-modules java.xml.bind" to your JVM.

1. Version Differences

Fulcro has undergone a number of face-lifts over its lifetime.

1.x

An add-on library for Om Next. Has various limitations as a result of this, and several bugs that are deep within Om Next (which was not getting updates).

2.0 - 2.4

No longer depends on Om Next. Public API is the same, except where improvements and fixes were needed.

2.5

Integrated co-located CSS for better support (e.g. localized-dom), made all server dependencies dynamic, rewrite of i18n. This version requires you to change a few i18n things if you were using it, and adds API to the DOM to make UI much more concise.

2.6

Changes for React 16+. Changes in React led to a change in the underlying rendering refresh optimizations.

3.0

This version. A nearly complete rewrite of the internals. Simplifies a lot of things, makes others more extensible, and preserves the shape of the API; however, it is not 100% API compatible with prior versions. In particular in the course of fixing some long-standing issues subtle semantic differences were desirable/unavoidable in the transaction processing system.

2. Trying it Out

The documentation is mostly correct in the Developer’s Guide.

defsc

The new defsc looks like the old one, and even does some of the same error checking (NOTE: there is a known bug in the new props destructuring checks I have not had time to fix…​the workaround is to use lambda query/ident if you run into it). However, defsc’s option map support user-additions. Anything you include in that map now appears (unaltered) in `component-options. This allows library authors to co-locate information on a component without having to modify the macro. The "magic" behaviors of query/ident/initial-state (and error checking) are still present for developer aid and bw compatibility. Some of the arities may be wrong. I was considering making them all lambdas at one point and may have screwed something up. Please report any issues.

Namespaces

All of the namespaces changed:

├── com
│   └── fulcrologic
│       └── fulcro
│           ├── algorithms
│           │   ├── application_helpers.cljc
│           │   ├── data_targeting.cljc       ; targeting for loads/mutations
│           │   ├── denormalize.cljc          ; db->tree
│           │   ├── form_state.cljc           ; from 2.x
│           │   ├── indexing.cljc             ; internal
│           │   ├── merge.cljc                ; merge-component!, etc.
│           │   ├── misc.cljc                 ; 2.x util, roughly
│           │   ├── normalize.cljc            ; tree->db
│           │   ├── scheduling.cljc           ; internal
│           │   ├── tempid.cljc               ; from 2.x
│           │   ├── transit.cljc              ; from 2.x
│           │   ├── tx_processing.cljc        ; internals of new transact!
│           │   └── tx_processing_debug.cljc  ; debugging util for internals of new transact!
│           ├── application.cljc              ; App constructor: fulcro-app
│           ├── components.cljc               ; get-query, get-ident, etc.
│           ├── data_fetch.cljc               ; from 2.x: load, load-data, etc.
│           ├── dom
│           │   ├── events.cljc               ; various helpers from 2.x
│           │   ├── html_entities.cljc
│           │   └── icons.cljc
│           ├── dom.clj                       ; DOM from 2.x
│           ├── dom.cljs
│           ├── dom_common.cljc
│           ├── dom_server.clj                ; DOM for SSR (NOT WORKING YET)
│           ├── inspect                       ; package of nses for talking to Chrome Inspect plugin
│           │   ├── diff.cljc
│           │   ├── preload.cljs              ; Use THIS for using Inspect.  Do NOT include the inspect dependency.
│           │   └── transit.cljs
│           ├── macros
│           │   ├── defmutation.clj           ; macro internals. Do not use.
│           │   └── defsc.clj
│           ├── mutations.cljc                ; client-side defmutation
│           ├── networking
│           │   ├── http_remote.cljs          ; normal client remote (no longer uses protocols)
│           │   ├── mock_server_remote.cljs   ; mock server for use in cljs
│           │   └── server_middleware.clj     ; middleware elements from 2.x for rolling your own server
│           ├── rendering
│           │   ├── ident_optimized_render.cljc  ; Default rendering optimization.  Not perfect yet, but fast.
│           │   └── keyframe_render.cljc         ; More like Fulcro 2.x rendering. Relies on shouldComponentUpdate for performance.
│           ├── routing
│           │   ├── dynamic_routing.cljc      ; Router from Incubator
│           │   └── union_router.cljc         ; Fulcro 2.x routers (old dynamic router untested, possibly broken)
│           ├── specs.cljc
│           └── ui_state_machines.cljc        ; From Incubator

2.1. Creating An Application

Fulcro applications no longer use anything from Om Next. There are no protocols, no reconciler, etc. A Fulcro application is implemented as a map, and that app is usable everywhere the reconciler was in 2.x. There is no need to store it in an atom because it uses atoms internally to deal with state.

For hot code reload, you should use defonce to make your application in some central location that can be used from any other namespace:

(ns my.app
  (:require
    [com.fulcrologic.fulcro.networking.http-remote :as fhr]
    [com.fulcrologic.fulcro.application :as app]
    [com.fulcrologic.fulcro.components :as comp]
    [com.fulcrologic.fulcro.data-fetch :as df]
    [my.app.ui :as ui]
    [taoensso.timbre :as log]))

(defonce app (app/fulcro-app {:remotes   {:remote (fhr/fulcro-http-remote {:url "/api"})}}))

At some point in your logic you will want to associate the root of your UI with the application via app/mount!:

(app/mount! app ui/Root "app")

2.2. Significant Changes

I call these significant more for their long-term implications than their impact on existing code. Most existing code will be trivial to port to Fulcro 3, and should operate without much further change; however, some of the "hard edges" of Fulcro 2 are solved by these changes, and as such they are "significant" in that sense.

2.2.1. Defsc

As mentioned earlier: defsc no longer uses protocols at all. The options map is "beefed up" by the defsc macro, but in fact you can simply create a "contructor function" and call configure-component! on it and pass a (non-magic) options map to create a component. The macro just helps you with typos and is easier to read.

This also means things like CSS can now be a pure library concern. In fact, the fulcro-garden-css library is where CSS functionality lives now.

Some things that were macros in Fulcro 2.x no longer need to be. The incubator dynamic routers are an example of this. The old union router is still a custom macro because it actually emits more than one thing. In most cases just changing the "missing macro" to plain defsc will make it work.

2.2.2. Transact Changes

The most significant change is in the internal plumbing of transact!, which is now in the component namespace. Transactions are now safe to submit from anywhere in the code base, even from within helpers that are running within swap! against the state atom!

The transact! function just puts the tx on a submission queue. That’s it. At some point (very soon) after submission Fulcro will process the current submissions into an active queue.

My intention is to make the transaction plumbing "pluggable" (it is already structured to be) so that various approaches to transaction semantics can be implemented as standard or even library concerns.

This simplifies a lot of things:

  • You no longer need ptransact!. Just embed a transact! in some part of the result-action (see below) of your mutation.

  • Timing issues in dynamic routing and ui state machines should be easier to avoid/solve.

  • You can submit transactions without using setTimeout and be sure they will activate in the order submitted.

2.2.3. Mutation Generalizations

Mutations have become an even more central notion in the library. All versions of Fulcro have actually treated loads internally as mutations, because in fact a load is a combination of some state changes (recording the fact that something is loading, i.e. load markers) and fetching the actual data.

Prior versions of Fulcro had Om Next structure in the middle. Version 3 does not. The logic in 3 is much more direct:

  • A transaction is written as it always has been

  • Each element of the transaction (mutations) can choose local and remote behaviors

  • Optimistic actions run first

  • Network actions go on a queue and run in order

All of that should sound pretty much identical to what you’ve been doing all along. The big difference is what happens next:

  • Network results are delivered to a new result-action of the mutation. If the user does not supply a result-action, then the defmutation macro supplies a default that behaves like Fulcro 2.

As a result any full-stack operation is completely under your control, and you can even "invent" new sections of the mutation that will appear as handlers in the env:

(defmutation do-thing [params]
  (action [env] ...optimistic actions...)
  (remote [env] true)
  (ok-action [env] ...your custom action type!...)
  (result-action [{:keys [result app handlers] :as env}]
    (let [{:keys [status-code body]} result
          {:keys [ok-action]} handlers]
      (if (= 200 status-code)
        (ok-action env)
        ...))))

This maintains backward compatibility while also giving you the power to implement things like pmutate from incubator without having to resort to magical transaction transforms. The fact that you can trigger new transactions from any part of that code means that chaining behaviors is now trivial and no longer needs the concept of ptransact! (though there is an :optimistic? false option of the new transact! that emulates that behavior.

Interestingly, this also makes it super easy to generalize the implementation of loads even more than before. Loads are now implemented internally something like this (simplified for ease of understanding):

(defmutation internal-load! [{:keys [query marker] :as params}]
  (action [{:keys [app]}] (set-load-marker! app marker :loading))
  (result-action [{:keys [result app] :as env}]
      (if (load-error? result)
        (load-failed! env params)
        (finish-load! env params))))
  (remote  [{:keys [ast]}] (eql/query->ast query)))
The data-fecth API (e.g. load) still exists, and is pretty much like it was. The primary change is boolean/in-place load markers are no longer supported.
The multimethod mutate is still at the center of this; however, the arguments have changed. The multimethod is sent only an env, which contains (→ env :ast :params).

2.3. Using Inspect

Do NOT include Fulcro Inspect as a dependency. Instead, Fulcro now includes the client-side code necessary to talk to the Chrome extension without pulling in all of inspect’s dependencies. Just add the following preload:

 :builds   {:app  {:target     :browser
                   ...
                   :devtools   {:preloads [com.fulcrologic.fulcro.inspect.preload]}}

At the time of this writing the db, transactions, and network tabs all mostly work. Expanding Inspect to include some cool new features is one of my top priorities. I want to support better helpers for UI state machines, perhaps some UI performance monitoring, etc.

2.4. Known Issues

  • If you use link/ident joins in UI queries the new optimal render may miss refreshes. Use with-optimized-renderer in application-helpers (see the doc string) to select keyframe rendering if you have problems.

  • The root component of the app won’t hot-code reload

  • Some lifecycle signature arities might have changed. I’m on the fence about which things defsc should make "close over" this and props. I’m leaning towards "everything except query/ident/initial-state is raw".

  • Some Inspect features are not implemented.

  • Inspect transactions incorrectly report the before/after db as the same, even though it is changing (this has to do with changes in how transactions are processed internally…​the inspect hook isn’t hooked up in the right places).

  • Easy server and some related server stuff is gone. I do not plan to add much, if any, of it back.

3. Status

Version 3 is now officially in Alpha. Most APIs have been ported, and some have even been tested ;)

Some code (CSS and websockets) were moved to external libraries to reduce dependencies.

The general road map (with status) is:

  • Rewrite Transaction Internals (100% done, but needs more integration testing)

    • Write tx processing that is extensible, and can support all currently-known use-cases (100%)

    • Make tx system pluggable (100%)

    • Support for new tx-combining at network layer (designed, not implemented)

    • New defmutation (possibly to be renamed) (100%)

      • Support for result-action (100%)

      • Support for "extensible" mutation semantics (100%)

      • Support for quote-free transactions (100%)

  • Network Layer (100%)

    • Write adapters or otherwise build new remote networking

  • Merge Logic (100%)

    • Split merge routines into easily reusable bits (100%)

    • Make it possible for users to easily choose/customize merge strategy (100%)

    • Figure out the right place to put helpers like integrate-ident, etc. (100%)

  • App DB normalization/denormalization (100%)

    • Improve performance of db→tree (100%, up to 6x faster)

    • Factor logic out into clear namespaces (100%)

    • Add better tests (100%)

  • Components

    • Support for React class-based components (100%)

      • Rewrite of defsc (100%)

        • Drop protocols (100%)

        • Support extensibility (90% complete)

    • Turn component-local CSS into a pure library concern (100%)

    • Turn i18n into a pure library concern (0%)

  • Move UI State Machines into this library (90%, needs more testing)

  • Move Dynamic Router into this library (90%, needs more testing)

  • Move to EQL as Source of AST logic (100%)

  • Minimize dependencies (100%)

  • Documentation

    • Rewrite Developer’s Guide (0%)

      • Port book examples

    • Record new YouTube videos (0%)

    • Write new README (50%)

    • Doll up docstrings (50%)

    • Ensure it all works with cljdocs (0%)

  • Tests

    • Port over tests from F2 (40%)

  • Inspect

    • Get basics working (100%)

    • Get full set of classic functionality (100%)

    • Add state machine tab (0%)

    • Other possible tabs (nice to have):

      • UI Performance measurement (0%)

      • Faster search (0%)

      • Improved data folding UI (0%)

      • Make transactions tab show more of tx (currently params always show as …​)

  • Specs

    • Add more specs to functions (20%)

    • Fix up spec definitions to prevent cljs code bloat (50%)

    • Fix up ghostwheel usage so it doesn’t bloat adv compile builds (10%)

  • Nice to Haves (depends a bit on contributors)

    • React Hooks-based defsc (designed and prototyped, but needs integration work)

Fulcro is:

Copyright (c) 2017, Fulcrologic, LLC The MIT License (MIT)

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Can you improve this documentation?Edit on GitHub

cljdoc is a website building & hosting documentation for Clojure/Script libraries

× close