This guide provides examples and patterns for converting React components between JavaScript/JSX and UIx (ClojureScript).
All snippets assume UIx has been required like this:
(:require [uix.core :as uix :refer [defui $]])
function MyComponent({ title, children }) {
return (
<div className="container">
<h1>{title}</h1>
{children}
</div>
);
}
// With hooks
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}
(defui MyComponent [{:keys [title children]}]
($ :div.container
($ :h1 title)
children))
;; With hooks
(defui Counter []
(let [[count set-count] (uix/use-state 0)]
($ :button
{:on-click #(set-count (inc count))}
(str "Count: " count))))
<div className="parent">
<Child prop1="value1" prop2={value2}>
<span>Inner content</span>
</Child>
</div>
($ :div.parent
($ child
{:prop1 "value1"
:prop2 value2}
($ :span "Inner content")))
function Container({ style, className, children }) {
return (
<div style={style} className={className}>
{children}
</div>
);
}
// Usage
<Container style={{margin: 10}} className="wrapper">
<p>Content</p>
</Container>
(defui Container [{:keys [style class children]}]
($ :div
{:style style
:class class}
children))
;; Usage
($ Container
{:style {:margin 10}
:class "wrapper"}
($ :p "Content"))
function Example() {
const [count, setCount] = useState(0);
const ref = useRef(null);
useEffect(() => {
document.title = `Count: ${count}`;
}, [count]);
return <div ref={ref}>{count}</div>;
}
(defui Example []
(let [[count set-count] (uix/use-state 0)
ref (uix/use-ref)]
(uix.core/use-effect
#(set! (.-title js/document) (str "Count: " count))
[count])
($ :div {:ref ref} count)))
function Button({ onClick }) {
return (
<button onClick={(e) => onClick(e.target.value)}>
Click me
</button>
);
}
(defui Button [{:keys [on-click]}]
($ :button
{:on-click #(on-click (.. % -target -value))}
"Click me"))
<div
className={`base-class ${active ? 'active' : ''}`}
style={{
backgroundColor: color,
fontSize: size + 'px'
}}>
Content
</div>
($ :div
{:className (str "base-class" (when active " active"))
:style {:background-color color
:font-size (str size "px")}}
"Content")
function Example() {
const inputRef = useRef(null);
useEffect(() => {
inputRef.current.focus();
}, []);
return <input ref={inputRef} />;
}
(defui Example []
(let [input-ref (uix.core/use-ref)]
(uix.core/use-effect
#(.focus @input-ref)
[])
($ :input {:ref input-ref})))
{isLoading ? <Spinner /> : <Content />}
{showMessage && <Message />}
($ :<>
(if is-loading
($ Spinner)
($ Content))
(when show-message
($ Message)))
<ul>
{items.map((item) => (
<li key={item.id}>{item.text}</li>
))}
</ul>
($ :ul
(for [item items]
($ :li {:key (:id item)}
(:text item))))
function Form() {
const [value, setValue] = useState("");
return (
<input
value={value}
onChange={e => setValue(e.target.value)}
/>
);
}
(defui Form []
(let [[value set-value] (uix/use-state "")]
($ :input
{:value value
:on-change #(set-value (.. % -target -value))})))
Component Definition
defui
with $
macroElement Creation
$
macro with keywordsProps
Children
Hooks
useX
namingStyle
When converting from React:
General Tips:
Props Spreading
{...props}
:& props
Ref Handling
useRef(null)
uix/use-ref
Effect Cleanup
Class Names
Remember to always test thoroughly after conversion, as subtle differences between the frameworks can lead to unexpected behavior.
Add this context to your project via the
ctxs
command line integration: