Converting Between React and UIx

A guide with examples for converting React components to UIx in ClojureScript.

Converting Between React and UIx

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 $]])

Table of Contents

Component Definition

React (JavaScript)

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>
  );
}

UIx

(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))))

Element Creation

React (JSX)

<div className="parent">
  <Child prop1="value1" prop2={value2}>
    <span>Inner content</span>
  </Child>
</div>

UIx

($ :div.parent
   ($ child
      {:prop1 "value1"
       :prop2 value2}
      ($ :span "Inner content")))

Props and Children

React

function Container({ style, className, children }) {
  return (
    <div style={style} className={className}>
      {children}
    </div>
  );
}

// Usage
<Container style={{margin: 10}} className="wrapper">
  <p>Content</p>
</Container>

UIx

(defui Container [{:keys [style class children]}]
  ($ :div 
     {:style style
      :class class}
     children))

;; Usage
($ Container
   {:style {:margin 10}
    :class "wrapper"}
   ($ :p "Content"))

Hooks

React

function Example() {
  const [count, setCount] = useState(0);
  const ref = useRef(null);
  
  useEffect(() => {
    document.title = `Count: ${count}`;
  }, [count]);
  
  return <div ref={ref}>{count}</div>;
}

UIx

(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)))

Event Handling

React

function Button({ onClick }) {
  return (
    <button onClick={(e) => onClick(e.target.value)}>
      Click me
    </button>
  );
}

UIx

(defui Button [{:keys [on-click]}]
  ($ :button 
     {:on-click #(on-click (.. % -target -value))}
     "Click me"))

Class and Style

React

<div 
  className={`base-class ${active ? 'active' : ''}`}
  style={{
    backgroundColor: color,
    fontSize: size + 'px'
  }}>
  Content
</div>

UIx

($ :div
   {:className (str "base-class" (when active " active"))
    :style {:background-color color
            :font-size (str size "px")}}
   "Content")

DOM References

React

function Example() {
  const inputRef = useRef(null);
  
  useEffect(() => {
    inputRef.current.focus();
  }, []);
  
  return <input ref={inputRef} />;
}

UIx

(defui Example []
  (let [input-ref (uix.core/use-ref)]
    (uix.core/use-effect
     #(.focus @input-ref)
     [])
    ($ :input {:ref input-ref})))

Common Patterns

Conditional Rendering

React

{isLoading ? <Spinner /> : <Content />}
{showMessage && <Message />}

UIx

($ :<>
  (if is-loading
    ($ Spinner)
    ($ Content))
  (when show-message
    ($ Message)))

Lists

React

<ul>
  {items.map((item) => (
    <li key={item.id}>{item.text}</li>
  ))}
</ul>

UIx

($ :ul
   (for [item items]
     ($ :li {:key (:id item)}
        (:text item))))

Forms

React

function Form() {
  const [value, setValue] = useState("");
  return (
    <input
      value={value}
      onChange={e => setValue(e.target.value)}
    />
  );
}

UIx

(defui Form []
  (let [[value set-value] (uix/use-state "")]
    ($ :input
       {:value value
        :on-change #(set-value (.. % -target -value))})))

Key Differences Summary

  1. Component Definition

    • React uses function declarations
    • UIx uses defui with $ macro
  2. Element Creation

    • React uses JSX
    • UIx uses $ macro with keywords
  3. Props

    • React uses camelCase
    • UIx supports kebab-case and camelCase
  4. Children

    • React uses special children prop
    • UIx uses children in props map
  5. Hooks

    • React uses useX naming
    • UIx provides Clojure-friendly wrappers with same names (but kebab-case)
  6. Style

    • React uses camelCase and JavaScript objects
    • UIx uses Clojure maps with kebab-case

Best Practices

  1. When converting from React:

    • Use kebab-case for props and style keys
    • Convert event handlers appropriately
    • Handle JavaScript interop carefully
    • Use the $ macro for all element creation
  2. General Tips:

    • Test thoroughly after conversion
    • Pay attention to state management differences
    • Handle refs and effects carefully
    • Maintain consistent naming conventions
    • Remember UIx closely mirrors React patterns

Common Gotchas

  1. Props Spreading

    • React: {...props}
    • UIx: :& props
  2. Ref Handling

    • React: useRef(null)
    • UIx: uix/use-ref
  3. Effect Cleanup

    • React: Return cleanup function
    • UIx: Handles undefined return automatically
  4. Class Names

    • React: className
    • UIx: class

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: