{"id":21657903,"url":"https://github.com/funcool/rumext","last_synced_at":"2025-06-28T11:37:20.000Z","repository":{"id":41551056,"uuid":"87646812","full_name":"funcool/rumext","owner":"funcool","description":"Simple and decomplected ui library for ClojureScript (based on React)","archived":false,"fork":false,"pushed_at":"2024-08-23T08:11:26.000Z","size":418,"stargazers_count":90,"open_issues_count":3,"forks_count":10,"subscribers_count":10,"default_branch":"v2","last_synced_at":"2024-10-28T13:27:12.202Z","etag":null,"topics":["clojurescript","react","react-hooks","simple","ui"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/funcool.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES.md","contributing":null,"funding":".github/FUNDING.yml","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},"funding":{"github":"niwinz","patreon":"niwinz"}},"created_at":"2017-04-08T16:13:13.000Z","updated_at":"2024-09-24T22:55:47.000Z","dependencies_parsed_at":"2024-08-22T12:32:22.388Z","dependency_job_id":null,"html_url":"https://github.com/funcool/rumext","commit_stats":{"total_commits":140,"total_committers":2,"mean_commits":70.0,"dds":"0.0071428571428571175","last_synced_commit":"188844cb9c467e06e27601f0f0d5f8b02357db5d"},"previous_names":[],"tags_count":30,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/funcool%2Frumext","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/funcool%2Frumext/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/funcool%2Frumext/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/funcool%2Frumext/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/funcool","download_url":"https://codeload.github.com/funcool/rumext/tar.gz/refs/heads/v2","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":226304156,"owners_count":17603522,"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","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","react","react-hooks","simple","ui"],"created_at":"2024-11-25T09:28:18.856Z","updated_at":"2025-06-28T11:37:19.993Z","avatar_url":"https://github.com/funcool.png","language":"Clojure","readme":"# rumext\n\nSimple and Decomplected UI library based on React \u003e= 18 focused on performance.\n\n## Installation\n\nAdd to `deps.edn`:\n\n```clojure\nfuncool/rumext\n{:git/tag \"v2.21\"\n :git/sha \"072d671\"\n :git/url \"https://github.com/funcool/rumext.git\"}\n```\n\n## User Guide\n\nRumext is a tool to build a web UI in ClojureScript.\n\nIt's a thin wrapper on [React](https://react.dev/) \u003e= 18, focused on\nperformance and offering a Clojure-idiomatic interface.\n\n**API Reference**: http://funcool.github.io/rumext/latest/\n\n\nIt uses Clojure macros to achieve the same goal as [JSX\nformat](https://react.dev/learn/writing-markup-with-jsx) without using anything\nbut the plain Clojure syntax. The HTML is expressed in a format inspired\nin [hiccup library](https://github.com/weavejester/hiccup), but with its own\nimplementation.\n\nHTML code is represented as nested arrays with keywords for tags and\nattributes. Example:\n\n```clojure\n[:div {:class \"foobar\"\n       :style {:background-color \"red\"}\n       :on-click some-on-click-fn}\n  \"Hello World\"]\n```\n\nMacros are smart enough to transform attribute names from `lisp-case`\nto `camelCase` and renaming `:class` to `className`. So the compiled javacript\ncode for this fragment could be something like:\n\n```js\nReact.createElement(\"div\",\n                    {className: \"foobar\",\n                     style: {\"backgroundColor\": \"red\"},\n                     onClick: someOnClickFn},\n                    \"Hello World\");\n```\n\nAnd this is what will be rendered when the app is loaded in a browser:\n\n```html\n\u003cdiv class=\"foobar\"\n     style=\"background-color: red\"\n     onClick=someOnClickFn\u003e\n  Hello World\n\u003c/div\u003e\n```\n\n**WARNING**: it is mainly implemented to be used in\n[Penpot](https://github.com/penpot/penpot) and released as separated project\nfor conveniendce. Don't expect compromise for backwards compatibility beyond\nwhat the penpot project needs.\n\n### Instantiating elements and custom components\n\n#### Passing props\n\nAs seen above, when using the [Hiccup-like](https://github.com/weavejester/hiccup)\nsyntax, you can create a HTML element with a keyword like `:div`, `:span` or\n`:p`. You can also specify a map of attributes, that are converted at compile\ntime into a Javascript object.\n\n**IMPORTANT**: a Javascript plain object is different from a Clojure plain map.\nIn ClojureScript you can handle mutable JS objects with a specific API, and\nconvert forth and back to Clojure maps. You can learn more about it in\n[ClojureScript Unraveled](https://funcool.github.io/clojurescript-unraveled/#javascript-objects)\nbook.\n\nRumext macros have some features to pass properties in a more convenient and\nClojure idiomatic way. For example, when using the `[:div {...}]` syntax, you\ndo not need to add the `#js` prefix, it's added automatically. There are also\nsome automatic transformations of property names:\n\n * Names in `lisp-case` are transformed to `camelCase`.\n * Reserved names like `class` are transformed to React convention, like\n   `className`.\n * Names already in `camelCase` are passed directly without transform.\n * Properties that begin with `data-` and `aria-` are also passed directly.\n * Transforms are applied only to `:keyword` properties. You can also send\n   string properties, that are not processed anyway.\n\nIt's important to notice that this transformations are performed at compile time,\nhaving no impact in runtime performance.\n\n\n#### Dynamic element names and attributes\n\nThere are times when we'll need the element name to be chosen dynamically or\nconstructed at runtime; the props to be built dynamically or created as an\nelement from a user-defined component.\n\nFor this purpose, Rumext exposes a special macro: `:\u003e`, a general-purpose\nhandler for passing dynamically defined props to DOM native elements or\ncreating elements from user-defined components.\n\nTo define the element dynamically, just pass a variable with the name as a\nfirst parameter of `:\u003e`.\n\n```clojure\n(let [element (if something \"div\" \"span\")]\n  [:\u003e element {:class \"foobar\"\n               :style {:background-color \"red\"}\n               :on-click some-on-click-fn}\n    \"Hello World\"])\n```\n\nTo give a dynamic map of properties, you may also give a variable as a\nsecond parameter:\n\n```clojure\n(let [props #js {:className \"fooBar\"\n                 :style #js {:backgroundColor \"red\"}\n                 :onClick some-on-click}]\n  [:\u003e \"div\" props\n    \"Hello World\"])\n```\n\n**IMPORTANT** if you define the attributes dynamically, outside the `:\u003e` macro,\nthere are no automatic transformations. So you need to define the map as a\nplain Javascript object with the `#js` prefix or any other way. You also need\nto use `camelCase` names and remember to use `className` instead of `class`,\nfor example.\n\nThere are a couple of utilities for managing dynamic attributes in a more\nconvenient way.\n\n\n##### `mf/spread-props`\n\nOr shorter alias: `mf/spread`\n\nA macro that allows performing a merge between two props data structures using\nthe JS spread operator (`{...props1, ...props2}`). This macro also performs\nname transformations if you pass a literal map as a second parameter.\n\nIt is commonly used this way:\n\n```clojure\n(mf/defc my-label*\n  [{:keys [name class on-click] :rest props}]\n  (let [class (or class \"my-label\")\n        props (mf/spread-props props {:class class})]\n    [:span {:on-click on-click}\n      [:\u003e :label props name]]))\n```\n\nVery similar to `mf/spread-props` but without react flavored props\ntransformations you have the `mf/spread-object`.\n\nIn both cases, if both arguments are symbols, no transformation\ncan be applied because is unknown the structure at compile time.\n\n\n##### `mf/props`\n\nA helper macro to create a Javascript props object from a Clojure map,\napplying name transformations.\n\nAn example of how it can be used and combined with `mf/spread-props`:\n\n```clojure\n(mf/defc my-label*\n  [{:keys [name class on-click] :rest props}]\n  (let [class (or class \"my-label\")\n        new-props (mf/props {:class class})\n        all-props (mf/spread-props props new-props)]\n    [:span {:on-click on-click}\n      [:\u003e :label props name]]))\n```\n\n\n##### `mf/object`\n\nA helper macro for create javascript objects from clojure literals. It works recursiverlly.\n\n```clojure\n(mf/object {:a [1 2 3]})\n\n;; Is analogous to\n#js {:a #js [1 2 3]}\n```\n\n\n##### `mfu/map-\u003eprops`\n\nIn some cases you will need to make props from a dynamic Clojure\nobject. You can use `mf/map-\u003eprops` function for it, but be aware that\nit makes the conversion to Javascript and the names transformations in\nruntime, so it adds some overhead in each render. Consider not using\nit if performance is important.\n\n```clojure\n(require '[rumext.v2.utils :as mfu])\n\n(let [clj-props {:class \"my-label\"}\n      props (mfu/map-\u003eprops clj-props)]\n  [:\u003e :label props name])\n```\n\n##### `mfu/bean`\n\nA helper that allows create a proxy object from javascript object that\nhas the same semantics as clojure map and clojure vectors. Allows\nhandle clojure and javascript parameters in a transparent way.\n\n```clojure\n(require '[rumext.v2.utils :as mfu])\n\n(mf/defc my-select*\n  [{:keys [options] :rest props}]\n  (let [options (mfu/bean options)\n        ;; from here, options looks like a clojure vector\n        ;; independently if it passed as clojure vector\n        ;; or js array.\n        ]\n    [:select ...]))\n```\n\n#### Instantiating a custom component\n\nYou can pass to `:\u003e` macro the name of a custom component (see [below](#creating-a-react-custom-component))\nto create an instance of it:\n\n```clojure\n(mf/defc my-label*\n  [{:keys [name class on-click] :rest props}]\n    [:span {:on-click on-click}\n      [:\u003e :label props name]])\n\n(mf/defc other-component*\n  []\n  [:\u003e my-label* {:name \"foobar\" :on-click some-fn}])\n```\n\n### Creating a React custom component\n\nThe `defc` macro is the basic block of a Rumext UI. It's a lightweight utility\nthat generates a React **function component** and adds some adaptations for it\nto be more convenient to ClojureScript code, like `camelCase` conversions and\nreserved name changes as explained [above](#passing-props).\n\nFor example, this defines a React component:\n\n```clojure\n(require '[rumext.v2 :as mf])\n\n(mf/defc title*\n  [{:keys [label-text] :as props}]\n  [:div {:class \"title\"} label-text])\n```\n\nThe compiled javascript for this block will be similar to what would be\nobtained for this JSX block:\n\n```js\nfunction title({labelText}) {\n  return (\n    \u003cdiv className=\"title\"\u003e\n      {labelText}\n    \u003c/div\u003e\n  );\n}\n```\n\n**NOTE**: the `*` in the component name is a mandatory convention for proper\nvisual distinction of React components and Clojure functions. It also enables\nthe current defaults on how props are handled. If you don't use the `*` suffix,\nthe component will behave in legacy mode (see the [FAQs](#faq) below).\n\nThe component created this way can be mounted onto the DOM:\n\n```clojure\n(ns myname.space\n  (:require\n   [goog.dom :as dom]\n   [rumext.v2 :as mf]))\n\n(def root (mf/create-root (dom/getElement \"app\")))\n(mf/render! root (mf/html [:\u003e title* {:label-text \"hello world\"}]))\n```\n\nOr you can use `mf/element`, but in this case you need to give the\nattributes in the raw Javascript form, because this macro does not have\nautomatic conversions:\n\n```clojure\n(ns myname.space\n  (:require\n   [goog.dom :as dom]\n   [rumext.v2 :as mf]))\n\n(def root (mf/create-root (dom/getElement \"app\")))\n(mf/render! root (mf/element title* #js {:labelText \"hello world\"}))\n```\n\n### Reading component props \u0026 destructuring\n\nWhen React instantiates a function component, it passes a `props` parameter\nthat is a map of the names and values of the attributes defined in the calling\npoint.\n\nNormally, Javascript objects cannot be destructured. But the `defc` macro\nimplements a destructuring functionality, that is similar to what you can do\nwith Clojure maps, but with small differences and convenient enhancements for\nmaking working with React props and idioms easy, like `camelCase` conversions\nas explained [above](#passing-props).\n\n```clojure\n(mf/defc title*\n  [{:keys [title-name] :as props}]\n  (assert (object? props) \"expected object\")\n  (assert (string? title-name) \"expected string\")\n  [:label {:class \"label\"} title-name])\n```\n\nIf the component is called via the `[:\u003e` macro (explained [above](#dynamic-element-names-and-attributes)),\nthere will be two compile-time conversion, one when calling and another one when\ndestructuring. In the Clojure code all names will be `lisp-case`, but if you\ninspect the generated Javascript code, you will see names in `camelCase`.\n\n#### Default values\n\nAlso like usual destructuring, you can give default values to properties by\nusing the `:or` construct:\n\n```clojure\n(mf/defc color-input*\n  [{:keys [value select-on-focus] :or {select-on-focus true} :as props}]\n  ...)\n```\n\n#### Rest props\n\nAn additional idiom (specific to the Rumext component macro and not available\nin standard Clojure destructuring) is the ability to obtain an object with all\nnon-destructured props with the `:rest` construct. This allows to extract the\nprops that the component has control of and leave the rest in an object that\ncan be passed as-is to the next element.\n\n```clojure\n(mf/defc title*\n  [{:keys [name] :rest props}]\n  (assert (object? props) \"expected object\")\n  (assert (nil? (unchecked-get props \"name\")) \"no name in props\")\n\n  ;; See below for the meaning of `:\u003e`\n  [:\u003e :label props name])\n```\n\n#### Reading props without destructuring\n\nOf course the destructure is optional. You can receive the complete `props`\nargument and read the properties later. But in this case you will not have\nthe automatic conversions:\n\n```clojure\n(mf/defc color-input*\n  [props]\n  (let [value            (unchecked-get props \"value\")\n        on-change        (unchecked-get props \"onChange\")\n        on-blur          (unchecked-get props \"onBlur\")\n        on-focus         (unchecked-get props \"onFocus\")\n        select-on-focus? (or (unchecked-get props \"selectOnFocus\") true)\n        class            (or (unchecked-get props \"className\") \"color-input\")\n```\n\nThe recommended way of reading `props` javascript objects is by using the\nClojurescript core function `unchecked-get`. This is directly translated to\nJavascript `props[\"propName\"]`. As Rumext is performance oriented, this is the\nmost efficient way of reading props for the general case. Other methods like\n`obj/get` in Google Closure Library add extra safety checks, but in this case\nit's not necessary since the `props` attribute is guaranteed by React to have a\nvalue, although it can be an empty object.\n\n#### Forwarding references\n\nIn React there is a mechanism to set a reference to the rendered DOM element, if\nyou need to manipulate it later. Also it's possible that a component may receive\nthis reference and gives it to a inner element. This is called \"forward referencing\"\nand to do it in Rumext, you need to add the `forward-ref` metadata. Then, the\nreference will come in a second argument to the `defc` macro:\n\n```clojure\n(mf/defc wrapped-input*\n  {::mf/forward-ref true}\n  [props ref]\n  (let [...]\n    [:input {:style {...}\n             :ref ref\n             ...}]))\n```\n\nIn React 19 this will not be necessary, since you will be able to pass the ref\ndirectly inside `props`. But Rumext currently only support React 18.\n\n### Props Checking\n\nThe Rumext library comes with two approaches for checking props:\n**simple** and **malli**.\n\nLet's start with the **simple**, which consists of simple existence checks or\nplain predicate checking. For this, we have the `mf/expect` macro that receives\na Clojure set and throws an exception if any of the props in the set has not\nbeen given to the component:\n\n```clojure\n(mf/defc button*\n  {::mf/expect #{:name :on-click}}\n  [{:keys [name on-click]}]\n  [:button {:on-click on-click} name])\n```\n\nThe prop names obey the same rules as the destructuring so you should use the\nsame names.\n\nSometimes a simple existence check is not enough; for those cases, you can give\n`mf/expect` a map where keys are props and values are predicates:\n\n```clojure\n(mf/defc button*\n  {::mf/expect {:name string?\n                :on-click fn?}}\n  [{:keys [name on-click]}]\n  [:button {:on-click on-click} name])\n```\n\nIf that is not enough, you can use `mf/schema` macro that supports\n**[malli](https://github.com/metosin/malli)** schemas as a validation\nmechanism for props:\n\n```clojure\n(def ^:private schema:props\n  [:map {:title \"button:props\"}\n   [:name string?]\n   [:class {:optional true} string?]\n   [:on-click fn?]])\n\n(mf/defc button*\n  {::mf/schema schema:props}\n  [{:keys [name on-click]}]\n  [:button {:on-click on-click} name])\n```\n\n**IMPORTANT**: The props checking obeys the `:elide-asserts` compiler\noption and by default, they will be removed in production builds if\nthe configuration value is not changed explicitly.\n\n### Hooks\n\nYou can use React hooks as is, as they are exposed by Rumext as\n`mf/xxx` wrapper functions. Additionaly, Rumext offers several\nspecific hooks that adapt React ones to have a more Clojure idiomatic\ninterface.\n\nYou can use both one and the other interchangeably, depending on which\ntype of API you feel most comfortable with. The React hooks are exposed\nas they are in React, with the function name in `camelCase`, and the\nRumext hooks use the `lisp-case` syntax.\n\nOnly a subset of available hooks is documented here; please refer to\nthe [React API reference\ndocumentation](https://react.dev/reference/react/hooks) for detailed\ninformation about available hooks.\n\n#### `use-state`\n\nThis is analogous to the `React.useState`. It offers the same\nfunctionality but uses the ClojureScript atom interface.\n\nCalling `mf/use-state` returns an atom-like object that will deref to\nthe current value, and you can call `swap!` and `reset!` on it to\nmodify its state. The returned object always has a stable reference\n(no changes between rerenders).\n\nAny mutation will schedule the component to be rerendered.\n\n```clojure\n(require '[rumext.v2 as mf])\n\n(mf/defc local-state*\n  [props]\n  (let [clicks (mf/use-state 0)]\n    [:div {:on-click #(swap! clicks inc)}\n      [:span \"Clicks: \" @clicks]]))\n```\n\nThis is functionally equivalent to using the React hook directly:\n\n```clojure\n(mf/defc local-state*\n  [props]\n  (let [[counter update-counter] (mf/useState 0)]\n    [:div {:on-click (partial update-counter #(inc %))}\n      [:span \"Clicks: \" counter]]))\n```\n\n#### `use-var`\n\nIn the same way as `use-state` returns an atom-like object. The unique\ndifference is that updating the ref value does not schedule the\ncomponent to rerender. Under the hood, it uses the `useRef` hook.\n\n**DEPRECATED:** should not be used\n\n#### `use-effect`\n\nAnalogous to the `React.useEffect` hook with a minimal call convention\nchange (the order of arguments is inverted).\n\nThis is a primitive that allows incorporating probably effectful code\ninto a functional component:\n\n```clojure\n(mf/defc local-timer*\n  [props]\n  (let [local (mf/use-state 0)]\n    (mf/use-effect\n      (fn []\n        (let [sem (js/setInterval #(swap! local inc) 1000)]\n          #(js/clearInterval sem))))\n    [:div \"Counter: \" @local]))\n```\n\nThe `use-effect` is a two-arity function. If you pass a single\ncallback function, it acts as though there are no dependencies, so the\ncallback will be executed once per component (analogous to `didMount`\nand `willUnmount`).\n\nIf you want to pass dependencies, you have two ways:\n\n- passing a JS array as a first argument (like in React but with\n    inverted order).\n- using the `rumext.v2/deps` helper:\n\n```clojure\n(mf/use-effect\n  (mf/deps x y)\n  (fn [] (do-stuff x y)))\n```\n\nAnd finally, if you want to execute it on each render, pass `nil` as\ndeps (much in the same way as raw `useEffect` works).\n\nFor convenience, there is an `mf/with-effect` macro that drops one\nlevel of indentation:\n\n```clojure\n(mf/defc local-timer*\n  [props]\n  (let [local (mf/use-state 0)]\n    (mf/with-effect []\n      (let [sem (js/setInterval #(swap! local inc) 1000)]\n        #(js/clearInterval sem)))\n    [:div \"Counter: \" @local]))\n```\n\nHere, the deps must be passed as elements within the vector (the first\nargument).\n\nObviously, you can also use the React hook directly via `mf/useEffect`.\n\n#### `use-memo`\n\nIn the same line as the `use-effect`, this hook is analogous to the\nReact `useMemo` hook with the order of arguments inverted.\n\nThe purpose of this hook is to return a memoized value.\n\nExample:\n\n```clojure\n(mf/defc sample-component*\n  [{:keys [x]}]\n  (let [v (mf/use-memo (mf/deps x) #(pow x 10))]\n    [:span \"Value is: \" v]))\n```\n\nOn each render, while `x` has the same value, the `v` only will be\ncalculated once.\n\nThis also can be expressed with the `rumext.v2/with-memo` macro that\nremoves a level of indentation:\n\n```clojure\n(mf/defc sample-component*\n  [{:keys [x]}]\n  (let [v (mf/with-memo [x]\n            (pow x 10))]\n    [:span \"Value is: \" v]))\n```\n\n#### `use-fn`\n\nIs a special case of `use-memo`in that the memoized value is a \nfunction definition.\n\nAn alias for `use-callback`, that is a wrapper on `React.useCallback`.\n\n#### `deref`\n\nA Rumext custom hook that adds reactivity to atom changes to the\ncomponent. Calling `mf/deref` returns the same value as the Clojure\n`deref`, but also sets a component rerender when the value changes.\n\nExample:\n\n```clojure\n(def clock (atom (.getTime (js/Date.))))\n(js/setInterval #(reset! clock (.getTime (js/Date.))) 160)\n\n(mf/defc timer*\n  [props]\n  (let [ts (mf/deref clock)]\n    [:div \"Timer (deref): \"\n     [:span ts]]))\n```\n\nInternally, it uses the `react.useSyncExternalStore` API together with\nthe ability of atom to watch it.\n\n### Higher-Order Components\n\nReact allows to create a component that adapts or wraps another component\nto extend it and add additional functionality. Rumext includes a convenient\nmechanism for doing it: the `::mf/wrap` metadata.\n\nCurrently Rumext exposes one such component:\n\n- `mf/memo`: analogous to `React.memo`, adds memoization to the\n  component based on props comparison. This allows to completely\n  avoid execution to the component function if props have not changed.\n\n```clojure\n(mf/defc title*\n  {::mf/wrap [mf/memo]}\n  [{:keys [name]}]\n  [:div {:class \"label\"} name])\n```\n\nBy default, the `identical?` predicate is used to compare props; you\ncan pass a custom comparator function as a second argument:\n\n```clojure\n(mf/defc title*\n  {::mf/wrap [#(mf/memo % =)]}\n  [{:keys [name]}]\n  [:div {:class \"label\"} name])\n```\n\nFor more convenience, Rumext has a special metadata `::mf/memo` that\nfacilitates the general case for component props memoization. If you\npass `true`, it will behave the same way as `::mf/wrap [mf/memo]` or\n`React.memo(Component)`. You also can pass a set of fields; in this\ncase, it will create a specific function for testing the equality of\nthat set of props.\n\nIf you want to create your own higher-order component, you can use the\n`mf/fnc` macro:\n\n```clojure\n(defn some-factory\n  [component param]\n  (mf/fnc my-high-order-component*\n    [props]\n    [:section\n     [:\u003e component props]]))\n```\n\n### FAQ\n\n#### Differences with RUM\n\nThis project was originated as a friendly fork of\n[rum](https://github.com/tonsky/rum) for a personal use but it later\nevolved to be a completly independent library that right now does not\ndepend on it and probably no longer preserves any of the original\ncode. In any case, many thanks to Tonksy for creating rum.\n\nThis is the list of the main differences:\n\n- use function based components instead of class based components.\n- a clojurescript friendly abstractions for React Hooks.\n- the component body is compiled statically (never interprets at\n  runtime thanks to **hicada**).\n- performance focused, with a goal to offer almost 0 runtime\n  overhead on top of React.\n\n\n#### Why the import alias is `mf` in the examples?\n\nThe usual convention of importing RUM project was to use `rum/defc` or\n`m/defc`. For Rumext the most straightforward abbreviation would have been\n`mx/defc`. But that preffix was already use for something else. So finally we\nchoose `mf/defc`. But this is not mandatory, it's only a convention we follow\nin this manual and in Penpot.\n\n\n#### What is the legacy mode?\n\nIn earlier versions of Rumext, components had a default behavior of\nautomatically converting the `props` Javascript object coming from\nReact to a Clojure object, so it could be read by normal destructuring\nor any other way of reading objects.\n\nAdditionally you could use `:\u0026` handler instead of `:\u003e` to give a\nClojure object that was converted into Javascript for passing it to\nReact.\n\nBut both kind of transformations were done in runtime, thus adding\nthe conversion overhead to each render of the compoennt. Since Rumex\nis optimized for performance, this behavior is now deprecated. With\nthe macro destructuring and other utilities explained above, you can\ndo argument passing almost so conveniently, but with all changes done\nin compile time.\n\nCurrently, components whose name does not use `*` as a suffix behave\nin legacy mode. You can activate the new behavior by adding the\n`::mf/props :obj` metadata, but all this is considered deprecated now.\nAll new components should use `*` in the name.\n\n## License\n\nLicensed under MPL-2.0 (see [LICENSE](LICENSE) file on the root of the repository)\n","funding_links":["https://github.com/sponsors/niwinz","https://patreon.com/niwinz"],"categories":["Clojure"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffuncool%2Frumext","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffuncool%2Frumext","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffuncool%2Frumext/lists"}