{"id":29130543,"url":"https://github.com/factorhouse/hsx","last_synced_at":"2025-10-05T20:50:01.259Z","repository":{"id":292980856,"uuid":"915853775","full_name":"factorhouse/hsx","owner":"factorhouse","description":"HSX is a ClojureScript library for writing React components using Hiccup syntax.","archived":false,"fork":false,"pushed_at":"2025-07-02T06:56:45.000Z","size":235,"stargazers_count":61,"open_issues_count":1,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-09-17T00:16:06.230Z","etag":null,"topics":["clojurescript","hiccup","react"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/factorhouse.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-01-13T00:47:58.000Z","updated_at":"2025-08-18T07:24:00.000Z","dependencies_parsed_at":"2025-05-13T05:36:35.029Z","dependency_job_id":null,"html_url":"https://github.com/factorhouse/hsx","commit_stats":null,"previous_names":["factorhouse/hsx"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/factorhouse/hsx","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/factorhouse%2Fhsx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/factorhouse%2Fhsx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/factorhouse%2Fhsx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/factorhouse%2Fhsx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/factorhouse","download_url":"https://codeload.github.com/factorhouse/hsx/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/factorhouse%2Fhsx/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278518641,"owners_count":26000177,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-05T02:00:06.059Z","response_time":54,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["clojurescript","hiccup","react"],"created_at":"2025-06-30T04:06:56.784Z","updated_at":"2025-10-05T20:50:01.239Z","avatar_url":"https://github.com/factorhouse.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cimg src=\"assets/hsx.svg\" alt=\"HSX\" width=\"240\"/\u003e\n\n[![test](https://github.com/factorhouse/hsx/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/factorhouse/hsx/actions/workflows/test.yml)\n[![Clojars Project](https://img.shields.io/clojars/v/io.factorhouse/hsx.svg)](https://clojars.org/io.factorhouse/hsx)\n\n**HSX** is a ClojureScript library for writing React components using [Hiccup syntax](https://github.com/weavejester/hiccup). We believe Hiccup is the most idiomatic (and joyful) way to express HTML in Clojure.\n\nThink of HSX as a lightweight syntactic layer over React, much like [JSX](https://react.dev/learn/writing-markup-with-jsx) in the JavaScript world.\n\n## Why HSX?\n\nHSX is designed to offer a seamless transition from Reagent to plain, idiomatic React Function components. It’s compatible with [Reagent-style Hiccup](https://github.com/reagent-project/reagent/blob/master/doc/UsingHiccupToDescribeHTML.md), making it trivial to migrate your existing codebase to HSX.\n\nUnlike Reagent, HSX does not:\n\n* Render components as classes (under the hood). HSX compiles to plain React Function components.\n* Include its own state abstractions like Ratoms and reactions. Use React’s built-in state management hooks like [useState](https://react.dev/reference/react/useState).\n\nIf you want to read more about the engineering challenge of moving a 120k LOC Reagent codebase to React 19 read [this blog post](https://factorhouse.io/blog/articles/beyond-reagent-with-hsx-and-rfx/).\n\n## Features\n\n* **Supports React 19:** hooks, effects, concurrent rendering, suspense, transitions, etc \n* **Hiccup Syntax**: Write React components with concise, readable Hiccup expressions.\n* **Minimal Overhead**: HSX is just a thin layer on top of React. No unnecessary abstractions or runtime complexities. No external dependencies.\n* **Migration-Friendly**: Drop-in compatibility with Reagent-style Hiccup makes it simple to upgrade existing codebases.\n\n## Motivation\n\n[Reagent](https://github.com/reagent-project/reagent) was ahead of its time, giving ClojureScript developers advanced tools like reactive atoms and declarative UI rendering. However, React has since evolved, and modern React features such as hooks and concurrent rendering are fundamentally incompatible with Reagent’s internals.\n\n### Challenges with Reagent\n\n* **React 19 Compatibility**: Reagent's rendering model [does not play nice](https://github.com/reagent-project/reagent/issues/597#issuecomment-1908054952) with current React versions.\n* **Technical Debt**: Continuing to depend on Reagent introduces maintenance challenges. Reagent depends on a version of React that is over three years old. Most of the React ecosystem is starting require React 18 at a minimum.\n\n## Usage\n\nUsing HSX is straightforward. The entire library is only about 300 lines of ClojureScript (with comments). HSX is designed to be as close to plain React as possible while retaining the expressive power of Hiccup and Clojure data structures.\n\nHSX exposes two primary functions:\n\n* `io.factorhouse.hsx.core/create-element` - like `react/createElement` but for HSX components\n* `io.factorhouse.hsx.core/reactify-component` - like `reagent.core/reactify-component`\n\n### Example\n\n```clojure\n(ns com.corp.my-hsx-ui\n  (:require [io.factorhouse.hsx.core :as hsx]\n            [\"react-dom/client\" :refer [createRoot]]))\n\n;; This is a HSX component\n(defn test-ui [props text]\n  [:div props\n   \"Hello \" text \"!\"])\n\n(defonce root\n  (createRoot (.getElementById js/document \"app\")))\n\n(defn init []\n  (.render root \n           (hsx/create-element \n             [test-ui {:on-click #(js/alert \"Clicked!\")} \n              \"prospective HSX user\"])))\n```\n\nSee the [examples](https://github.com/factorhouse/hsx/tree/main/examples/hsx) directory for more examples.\n\n## Migrating from Reagent\n\nIf you have an existing Reagent codebase, the following `reagent.core` functions map to:\n\n| Reagent | HSX |\n|----------|-------------|\n| `reagent.core/as-element` | `io.factorhouse.hsx.core/create-element` |\n| `reagent.core/reactify-component` | `io.factorhouse.hsx.core/reactify-component` |\n| `reagent.core/create-element` | `react/createElement` |\n\n## FAQs\n\n### What about performance?\n\nWhen migrating from Reagent you will objectively find performance wins for your application by:\n\n- **Embracing concurrent rendering** - allowing React to [interrupt, schedule, and batch updates more intelligently](https://vercel.com/blog/how-react-18-improves-application-performance).\n- **Eliminating class-based components**, which Reagent relied on under the hood, removing unnecessary rendering layers (via `:f\u003e`) and improved interop with React libraries.\n- **Fixing long-standing Reagent interop quirks** — such as the well-documented [controlled input hacks](https://github.com/reagent-project/reagent/issues/619).\n\nWhen profiling our real-world, enterprise grade product ([Kpow](https://factorhouse.io/kpow)) we saw 4x fewer commits without the overall render duration blowing out after switching to HSX. More details [here](https://factorhouse.io/blog/articles/beyond-reagent-with-hsx-and-rfx/).\n\n### What about Ratoms (local state)?\n\nHSX components are just React function components under the hood with a bit of syntactic sugar. \n\nThere are no state abstractions found in this library. We suggest you migrate any Reagent components with local state to use `react/useState`. The `useState` hook is the most idiomatic way to deal with local state in React.\n\n```clojure\n;; (:require [\"react\" :as react])\n\n(defn reagent-component-with-local-state []\n  (let [state (reagent.core/atom 1)]\n    (fn []\n      [:div {:on-click #(swap! state inc)} \n       \"The value of state is \" @state])))\n\n(defn hsx-component-with-local-state []\n  (let [[state set-state] (react/useState 1)]\n    [:div {:on-click #(set-state inc)} \n     \"The value of state is \" state]))\n```\n\n### What about re-frame?\n\nWe have a companion library named [RFX](https://github.com/factorhouse/rfx) which is a drop-in replacement for re-frame without the dependency on Reagent.\n\nSee the [RFX repo](https://github.com/factorhouse/rfx) for more details.\n\n### What about global application state?\n\nIf RFX is overkill for your application (or you have bespoke requirements), you can use standard React solutions for global application state management like:\n\n* [useSyncExternalStore](https://react.dev/reference/react/useSyncExternalStore) hook to subscribe to an external store (such as a plain Clojure atom or even a [Datascript database](https://github.com/tonsky/datascript)).\n* Solutions found in the JS ecosystem like [zustand](https://github.com/pmndrs/zustand).\n* Using React [Reducer and Context](https://react.dev/learn/scaling-up-with-reducer-and-context) APIs.\n\n### What about hot-reloading?\n\nUsing [shadow-cljs](https://github.com/thheller/shadow-cljs) add a reload function like:\n\n```clojure\n(defn ^:dev/after-load reload []\n  (hsx/memo-clear!))\n```\n\nThis will clear the component cache after a code change.\n\n### How are props handled?\n\nExactly the same as Reagent:\n\n```clojure\n[:div {:on-click #(js/alert \"Clicked!\")}]\n```\n\nWould translate to:\n\n```clojure\n[:div #js {\"onClick\" #(js/alert \"Clicked!\")}]\n```\n\nWe use the same props serialization logic as Reagent to make migrating to HSX as pain-free as possible.\n\n### What about component metadata (keys, etc)\n\nThe same as Reagent - use Clojure metadata. Say you want to pass a React key to a component:\n\n```clojure\n(defn component-with-seq []\n  [:ol {:className \"bg-slate-500\"}\n   (for [item items]\n     ^{:key (str \"item-\" (:id item))}\n     [item-component item])])\n```\n\n### What about `id` and `className` short-hands?\n\nThe same as Reagent + Hiccup:\n\n```clojure\n[:div#foo ...] ;; =\u003e [:div {:id \"foo\"}]\n[:div.foo.bar ...] ;; =\u003e [:div {:className \"foo bar\"}]\n```\n\n### What about Reagent class-based components?\n\nClass based components (the ones with lifecycle methods) have been out of style for almost a decade with React.\n\nIf you wish to adopt HSX you will need to migrate Reagent class components to function components. Generally this means rewriting the component to use hooks. \n\nHowever, [error boundaries](https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary) are the one place in the React ecosystem where class components may be required. We suggest using a wrapping library like [react-error-boundary](https://github.com/bvaughn/react-error-boundary) instead.\n\nIf you still require class-based components, you can always extend `js/React.Component.prototype` yourself. See [this gist](https://gist.github.com/pesterhazy/2a25c82db0519a28e415b40481f84554) for an example.\n\n### Are components memoized (like Reagent's implicit `componentDidUpdate` logic)?\n\nBy default, yes, HSX components are wrapped in a [react/memo](https://react.dev/reference/react/memo) call with an appropriate `arePropsEqual?` predicate for ClojureScript data structures. \n\n**Note**: unlike Reagent, memoization is a performance optimization. Please refer to the [official React documentation](https://react.dev/reference/react/memo) for more information.\n\nIf you'd like to disable memoization by default globally, you can:\n\n```clojure\n{...\n :builds\n {:app\n  {:target :browser\n   :modules {:app {:entries [your.app]}}\n\n   :closure-defines {io.factorhouse.hsx.core/USE_MEMO false}\n   \n   }}}\n```\n\nIf you'd like to disable/enable memoization per-component, you can supply a `:memo?` key as metadata to the component vector:\n\n```clojure\n[:div \n ^{:memo? false} \n [my-hsx-comp arg1 arg2]]\n```\n\nIf you want to use a custom `are-props-equal?` predicate for memoization, you can also use component metadata:\n\n```clojure\n;; This custom predicate treats the previous and next state as equal if the value of `:foo` has not changed.\n(defn custom-are-props-equal-pred\n  [[prev-arg1 _prev-arg2] [next-arg1 _next-arg2]]\n  (= (:foo prev-arg1) (:foo next-arg1)))\n\n[:div\n ^{:memo? true :memo/predicate custom-are-props-equal-pred}\n [my-hsx-comp arg1 arg2]]\n```\n\n### What about Fragments?\n\nThe same as Reagent. Denoted by `:\u003c\u003e`\n\n```clojure\n(defn list-of-things [] \n  [:\u003c\u003e \n   [:div \"First thing\"]\n   [:div \"Second thing\"]])\n```\n\n### How do I call JavaScript components?\n\nThe same as Reagent. Denoted by `:\u003e`\n\n```clojure\n;; (:require [\"react-select\" :as Select])\n\n(defn dropdown-example [options]\n  [:\u003e Select {:on-change #(js/alert \"Data changed\")\n              :options options}])\n```\n\n### Is there a way to bypass JavaScript component props serialization?\n\nYes, pass a JS Object instead:\n\n```clojure\n;; (:require [\"react-select\" :as Select])\n\n(defn dropdown-example [options]\n  [:\u003e Select #js {\"onChange\" #(js/alert \"Data changed\") \n                  \"options\" options}])\n```\n\n### How do I use a HSX component from within a JavaScript component?\n\nUse `hsx/reactify-component`. If we use [react-error-boundary](https://github.com/bvaughn/react-error-boundary) as an example:\n\n```clojure\n;; (:require [\"react-error-boundary\" :refer [ErrorBoundary]] \n;;           [io.factorhouse.hsx.core :as hsx])\n\n(defn fallback-renderer \n  [{:keys [error]}]\n  [:div (str \"Something went wrong: \" error)])\n\n(defn with-error-boundary \n  [comp]\n  [:\u003e ErrorBoundary {:fallbackRender (hsx/reactify-component fallback-renderer)}\n   (hsx/create-element comp)])\n\n(defn my-ui []\n  [with-error-boundary \n   [:div \"This is my application...\"]])\n```\n\n## Copyright and License\n\nCopyright © 2025 Factor House Pty Ltd.\n\nDistributed under the Apache-2.0 License, the same as Apache Kafka.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffactorhouse%2Fhsx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffactorhouse%2Fhsx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffactorhouse%2Fhsx/lists"}