SkillAgentSearch skills...

Dumdom

Efficiently render and re-render immutable data

Install / Use

/learn @cjohansen/Dumdom
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

dumdom - The dumb DOM component library

dumdom is a component library that renders (and re-renders) immutable data efficiently. It delivers on the basic value proposition of React and its peers while eschewing features like component local state and object oriented APIs, and embracing ClojureScript features like immutable data structures.

dumdom is API compatible with Quiescent, and can be used as a drop-in replacement for it so long as you don't use React features directly. Refer to differences from React for things to be aware of.

dumdom is currently a wrapper for Snabbdom, but that should be considered an implementation detail, and may be subject to change. Using snabbdom features not explicitly exposed by dumdom is not recommended.

dumdom aims to be finished, stable, and worthy of your trust. Breaking changes will never be intentionally introduced to the codebase. For this reason, dumdom does not adhere to the "semantic" versioning scheme.

In addition to being API compatible with Quiescent, dumdom supports:

  • Hiccup syntax for components
  • Event handlers as data
  • Rendering to strings (useful for server-side rendering from both the JVM and node.js)
  • Efficient "inflation" of server-rendered markup on the client side

What about Replicant?

Replicant is Dumdom's spiritual successor. If you intend to start a new project, start it with Replicant. As promised, Dumdom is finished, stable and worthy of your trust. It's fine to use. However, Replicant was designed from the ground up after years of using Dumdom and is both pure Clojure(Script) (no JS dependencies) and a more pure implementation of the same core ideas.

Table of contents

Install

With tools.deps:

cjohansen/dumdom {:mvn/version "2024.04.02"}

With Leiningen:

[cjohansen/dumdom "2024.04.02"]

Example

Using hiccup-style data:

(require '[dumdom.core :as dumdom :refer [defcomponent]])

(defcomponent heading
  :on-render (fn [dom-node val old-val])
  [data]
  [:h2 {:style {:background "#000"}} (:text data)])

(defcomponent page [data]
  [:div
    [heading (:heading data)]
    [:p (:body data)]])

(dumdom/render
 [page {:heading {:text "Hello world"}
        :body "This is a web page"}]
 (js/document.getElementById "app"))

Using the Quiescent-compatible function API:

(require '[dumdom.core :as dumdom :refer [defcomponent]]
         '[dumdom.dom :as d])

(defcomponent heading
  :on-render (fn [dom-node val old-val])
  [data]
  (d/h2 {:style {:background "#000"}} (:text data)))

(defcomponent page [data]
  (d/div {}
    (heading (:heading data))
    (d/p {} (:body data))))

(dumdom/render
 (page {:heading {:text "Hello world"}
        :body "This is a web page"})
 (js/document.getElementById "app"))

Rationale

Of the many possible options, Quiescent is to me the perfect expression of "React in ClojureScript". It's simple, light-weight, does not allow component-local state, and pitches itself as strictly a rendering library, not a state management tool or UI framework.

While Quiescent has been done (as in "complete") for a long time, it is built on React, which is on a cycle of recurring "deprecations" and API changes, making it hard to keep Quiescent up to date with relevant security patches etc. At the same time, React keeps adding features which are of no relevance to the API Quiescent exposes, thus growing the total bundle size for no advantage to its users.

dumdom provides the same API as that of Quiescent, but does not depend on React. It aims to be as stable and complete as Quiescent, but still be able to ship occasional security patches as they are made to the underlying virtual DOM library. dumdom aims to reduce the amount of churn in your UI stack.

Limitations

Because dumdom is not based on React, you opt out of the "React ecosystem" entirely by using it. If you depend on a lot of open source/shared React components, or other React-oriented tooling, dumdom might not be the best fit for you.

Because dumdom does not offer any kind of component local state, it cannot be used as a holistic UI framework - it's just a rendering library. It does not come with any system for routing, dispatching actions, or managing state (either inside or outside of components), and is generally a batteries-not-included tool. I consider this a strength, others may see it differently.

Differences from Quiescent

Dumdom strives to be API compliant with Quiescent to the degree that it should be a drop-in replacement for Quiescent in any project that does not rely explicitly on any React APIs or third-party components. It does not necessarily commit to all the same restrictions that the Quiescent API imposes. The following is a list of minor differences between the two:

  • Quiescent does not allow the use of :on-render along with either of :on-mount and :on-update. Dumdom acknowledges that some components will implement :on-render and :on-mount or :on-update, and allows this.
  • Dumdom doesn't really care about TransitionGroup. You are free to use them, but the animation callbacks will work equally well outside TransitionGroup. This may cause breakage in some cases when porting from Quiescent to Dumdom. The risk is pretty low, and the upside is significant enough to allow Dumdom to take this liberty.

Differences from React

In React, onChange is really onInput. This is not true in dumdom. When swapping out Quiescent and React for dumdom, you must replace all occurrences of onChange with onInput to retain behavior.

Using with Devcards

Devcards is a system for rendering React components in isolation. Because dumdom components are not React components, they need some wrapping for Devcards to make sense of them.

You need to add dumdom-devcards as a separate dependency. Then use the dumdom.devcards namespace just like you would devcards.core:

(require '[dumdom.devcards :refer-macros [defcard]])

(defcard my-dumdom-card
  (my-dumdom-component {:value 0}))

Linting

This library exports clj-kondo configuration for linting the defcomponent macro. You may need to import the config. If you are using clojure-lsp, this should happen automatically.

If you are not using clj-kondo, you could get away with using a defcomponent macro that supports linting as defn. Like this one. It won't help you spot errors with using the wrong dumdom component options, but at least will silence false positive warnings.

Contribute

Feel free to report bugs and, even better, provide bug fixing pull requests! Make sure to add tests for your fixes, and make sure the existing ones stay green before submitting fixes.

make test

You can also run the tests in a browser with figwheel, which might be more useful during development:

clojure -A:dev:repl

Then open http://localhost:9595/figwheel-extra-main/tests.

If you're not yet sure how to formulate a test for your feature, fire up http://localhost:9595/ and play around in ./dev/dumdom/dev.cljs until you figure it out. More visually oriented code can be tested with devcards instead. Add a devcard to ./devcards/dumdom, and inspect the results at http://localhost:9595/devcards.html

If you have ideas for new features, please open an issue to discuss the idea and the API before implementing it to avoid putting lots of work into a pull request that might be rejected. I intend to keep dumdom a focused package, and don't want it to accrete a too wide/too loosely coherent set of features.

Running from Emacs

There is a .dir-locals.el file in the root of this repo to help you out. Run cider-jack-in-cljs, and you should get a REPL and figwheel running on port 9595:

Documentation

The vast majority of use-cases are covered by using hiccup-style markup for DOM elements, defining custom components with defcomponent, and rendering the resulting virtual DOM to an element with render:

(require '[dumdom.core :as dumdom :refer [defcomponent]])

(defcomponent my-component [data]
  [:div
    [:h1 "Hello world!"]
    [:p (:message data)]])

(dumdom/render
  (my-component {:message "Hello, indeed"})
  (js/document.getElementById "app"))

Components defined by defcomponent are functions, as demonstrated in the above example. You can also use them for hiccup markup, e.g.:

(dumdom/render
  [my-component {:message "Hello, indeed"}]
  (js/document.getElem
View on GitHub
GitHub Stars170
CategoryDevelopment
Updated3mo ago
Forks10

Languages

Clojure

Security Score

92/100

Audited on Dec 12, 2025

No findings