Reagent Documentation

repository·master·Indexed 26 days ago

https://github.com/reagent-project/reagent

A simple ClojureScript interface to React that enables the creation of efficient components using Hiccup-like markup and specialized atoms for reactive state management. It supports various environments including Browser, Node, Electron, and React Native, and provides APIs for asynchronous rendering synchronization via flush, next-tick, and after-update.

Tokens
15.9K
Snippets
54
Records
83
Agent score
88%

What's inside Reagent

  1. Overview of Reagent component forms

    master

    Reagent components are built around a mandatory render function that transforms data into Hiccup (HTML data structures). There are three primary ways to define a component, increasing in complexity:

    1. Form-1 (Simple Function): A direct function where data is passed as parameters and the return value is the Hiccup HTML.
    2. Form-2 (Function returning a Function): An outer function used for setup/state initialization that returns an inner render function. This allows the renderer to close over local state.
    3. Form-3 (Map of functions): A map containing a render function and optional React lifecycle methods for advanced interventions.
  2. Use component initialization functions

    master

    To perform setup when a component is first created, a component function can return a new function. This returned function is called for the actual rendering, allowing you to avoid React lifecycle callbacks like getInitialState or componentWillMount in most cases.

    (defn timer-component []
      (let [seconds-elapsed (r/atom 0)]
        (fn []
          (js/setTimeout #(swap! seconds-elapsed inc) 1000)
          [:div
           "Seconds Elapsed: " @seconds-elapsed])))
  3. Understand Reagent Component Reactivity

    master

    Reagent Components are reactive, meaning their render functions automatically re-run when their input data changes. This process follows a specific cycle:

    1. Input Data Changes: Either props or ratoms change.
    2. Renderer Re-run: The component's render function executes again.
    3. Hiccup Generation: The function produces new hiccup (HTML representation).
    4. Interpretation: Reagent interprets the new hiccup and updates the actual HTML in the DOM.
  4. Trigger Re-renders with Ratoms

    master

    A Component is reactive to ratoms if it dereferences them (using the @ symbol) within its render function. Reagent detects this dependency and watches the ratom for changes.

    Key behaviors:

    • Detection: Reagent identifies which components depend on which ratoms.
    • Change Detection: When a ratom is updated (e.g., via reset! or swap!), Reagent checks if the new value is different from the old value using the = operator (as of Reagent 0.6.0). If the value has changed, the component re-renders.

    Example of a component reacting to a ratom:

    (def name (reagent.ratom/atom "Bear"))
    
    (defn ask-for-forgiveness
      []
      [:div "Please " @name " with me"]) ;; Dereferencing @name triggers reactivity
    (def name  (reagent.ratom/atom "Bear"))
    
    (defn ask-for-forgiveness
      []           ;; <--- no props     
      [:div "Please " @name " with me"])   ;; notice that @
  5. Understand Hiccup syntax in Reagent

    master

    Reagent uses Hiccup, a nested ClojureScript vector structure, to describe HTML elements and components.

    General Rules:

    1. First element: A keyword (e.g., :div) represents an HTML tag. A symbol (e.g., my-component) represents a user-defined component.
    2. Second element (optional): A map representing attributes.
    3. Subsequent elements: Either Hiccup vectors (child nodes) or string literals (text nodes).

    Non-standard HTML attributes: To use non-standard attributes, use a string key in the attribute map instead of a keyword:

    [:span {"custom-attribute" "value"}]
    [:div {:class "parent"}
      [:p {:id "child-one"} "I'm first child element."]
      [:p "I'm the second child element."]]
  6. Configure React dependencies for Reagent 1

    master

    When using Reagent 1 with React 18 or older, you must provide the React dependencies using Cljsjs React packages.

    Note that Reagent 1 is tested against React 18 using compatibility mode (not using createRoot or concurrent mode), though it should remain compatible with other versions.

    [cljsjs/react "18.3.1-1"]
    [cljsjs/react-dom "18.3.1-1"]
  7. Upgrade to Reagent 0.8

    master

    Upgrading to Reagent 0.8 requires environment-specific changes depending on whether you use Cljsjs or npm modules, and whether your target is the Browser, Node, Electron, or React Native.

    Compatibility Matrix

    EnvironmentBuild TypeCljsjsNode Modules
    Browser:noneSupportedRequires Cljs 1.10.312
    Browser:advancedSupportedRequires Cljs 1.10.312
    Node:noneRequires Cljs 1.10.238+Supported
    Node:advancedRequires Cljs 1.10.238+Supported
  8. Optimize re-renders using square bracket [] syntax

    master

    In Reagent, using square brackets [] for component calls creates independent React components that only re-render when their props or referenced atoms change. This is significantly more efficient than using parentheses () (which returns a raw hiccup/data structure), as it allows Reagent to skip unnecessary re-renders of sub-trees where props remain identical.

    Key Difference:

    • Using [] (Component syntax): Reagent checks if props have changed. If they haven't, it skips the component's execution entirely. Only the specific parts of the tree that changed are re-rendered.
    • Using () (Hiccup syntax): The function always executes and returns a new data structure. This forces React to perform a full diff of the entire returned tree to find changes, which is computationally more expensive.
  9. Configure Reagent for Browser with Cljsjs

    master

    Using Reagent with Cljsjs packages is the recommended setup for the browser. No major changes are required, but ensure you update your Cljsjs React dependencies if you have direct dependencies to them.

    To force the ClojureScript compiler to use Cljsjs libraries instead of looking into the node_modules directory, use the :npm-deps false compiler option.

  10. Configure Reagent for Browser with Node Modules

    master

    If react, react-dom, and create-react-class are present in your node_modules directory, the ClojureScript compiler will use them with Reagent.

    Key Compiler Options

    • Automatic Installation: Use :npm-deps and :install-deps to have ClojureScript manage package installation automatically.
    • Environment Variables: Use :process-shim to provide a process.env.NODE_ENV constant. The compiler automatically sets this to production during :advanced optimizations, which enables the React production build.
    • Module Splitting: Module processing allows you to split output into several modules.

    Important: Externs

    Externs are required when using node modules. Because React accesses statically created objects dynamically, the Closure compiler may rename these properties and break the application. You must define externs to prevent this renaming.

  11. Use shorthand notation for `id` and `class`

    master

    Reagent provides shorthand syntax for common attributes:

    • ID: Use a hash (#) after the element name: [:div#my-id] is equivalent to [:div {:id "my-id"}].
    • Classes: Use a dot (.) followed by the class name: [:div.my-class] is equivalent to [:div {:class ["my-class"]}].
    • Combined: The ID must be listed before classes: [:div#my-id.my-class] is equivalent to [:div {:id "my-id" :class ["my-class"]}].
    • Nested Elements: Use the > character to stack elements: [:div>p>b "Text"] is equivalent to nested vectors.
    [:div#my-id.my-class.my-other-class]
    [:div>p>b "Nested Element"]
  12. Use the `reagent.dom.client` API for React 19

    master

    When using React 19, you should replace the legacy reagent.dom/render calls with the new reagent.dom.client API. This involves creating a root using create-root and then calling render on that root.

    (ns example.core
      (:require [reagent.dom.client :as rdomc]))
    
    (defn view []
      [:div "Hello world"])
    
    (defonce root (rdomc/create-root (.getElementById js/document "app")))
    
    (defn ^:export run []
      (rdomc/render root [view]))