|
| 1 | +# PROJECT SUMMARY: Reagent |
| 2 | + |
| 3 | +## Overview |
| 4 | +Reagent is a minimalistic ClojureScript interface to React that provides a functional, reactive programming model for building web user interfaces. It uses Hiccup-style syntax (vectors) to represent React components and provides reactive atoms (ratoms) for state management with automatic re-rendering. |
| 5 | + |
| 6 | +## Key Features |
| 7 | +- **Hiccup-style templating**: Write HTML using Clojure vectors `[:div "Hello"]` |
| 8 | +- **Reactive programming**: Built-in reactivity using ratoms that automatically trigger re-renders |
| 9 | +- **React interoperability**: Easy integration with existing React components |
| 10 | +- **Minimal overhead**: Thin wrapper around React with small footprint |
| 11 | +- **Functional approach**: Immutable data structures and functional components |
| 12 | + |
| 13 | +## Project Structure |
| 14 | + |
| 15 | +### Core Source Files (`src/reagent/`) |
| 16 | +- **`core.cljs`**: Main public API with 30+ functions including `atom`, `create-element`, `as-element`, `track`, `cursor`, `wrap`, `class-names`, `merge-props`, and React interop utilities |
| 17 | +- **`core.clj`**: Macro definitions for ClojureScript compilation |
| 18 | +- **`ratom.cljs`**: Reactive atom implementation with dependency tracking and batched updates |
| 19 | +- **`dom.cljs`**: DOM rendering functions, React DOM integration |
| 20 | +- **`debug.cljs`**: Development debugging utilities with assertion macros |
| 21 | + |
| 22 | +### Implementation Details (`src/reagent/impl/`) |
| 23 | +- **`template.cljs`**: Hiccup vector to React element conversion logic |
| 24 | +- **`component.cljs`**: React component lifecycle and state management |
| 25 | +- **`batching.cljs`**: Update batching system for performance optimization |
| 26 | +- **`util.cljs`**: Utility functions for props merging and component helpers |
| 27 | +- **`input.cljs`**: Input component handling for controlled components |
| 28 | +- **`protocols.cljs`**: Core protocols for reactive atoms and components |
| 29 | + |
| 30 | +### Configuration & Dependencies |
| 31 | +- **`project.clj`**: Leiningen build configuration with ClojureScript setup |
| 32 | +- **`deps.edn`**: tools.deps configuration for modern Clojure CLI workflows |
| 33 | +- **`shadow-cljs.edn`**: Shadow CLJS build configuration with browser, test, and prerender targets |
| 34 | +- **`bb.edn`**: Babashka task configuration for development workflows |
| 35 | +- **`examples/`**: Sample applications demonstrating different Reagent patterns |
| 36 | + |
| 37 | +## Key Dependencies |
| 38 | +- **ClojureScript 1.11.132**: Core language platform |
| 39 | +- **React 18.2.0-1**: Underlying React framework via cljsjs |
| 40 | +- **React DOM 18.2.0-1**: DOM rendering support via cljsjs |
| 41 | + |
| 42 | +## Core APIs and Usage Patterns |
| 43 | + |
| 44 | +### Basic Component Creation |
| 45 | +```clojure |
| 46 | +(ns my-app.core |
| 47 | + (:require [reagent.core :as r] |
| 48 | + [reagent.dom :as rdom])) |
| 49 | + |
| 50 | +;; Simple component |
| 51 | +(defn hello-world [] |
| 52 | + [:div "Hello, world!"]) |
| 53 | + |
| 54 | +;; Component with parameters |
| 55 | +(defn greeting [name] |
| 56 | + [:h1 "Hello, " name "!"]) |
| 57 | +``` |
| 58 | + |
| 59 | +### Reactive State Management |
| 60 | +```clojure |
| 61 | +;; Create reactive atom |
| 62 | +(def app-state (r/atom {:counter 0})) |
| 63 | + |
| 64 | +;; Component using reactive state |
| 65 | +(defn counter [] |
| 66 | + [:div |
| 67 | + [:p "Count: " (:counter @app-state)] |
| 68 | + [:button {:on-click #(swap! app-state update :counter inc)} "+"]]) |
| 69 | +``` |
| 70 | + |
| 71 | +### Advanced Reactive Features |
| 72 | +```clojure |
| 73 | +;; Computed values that update automatically |
| 74 | +(def doubled-counter (r/track #(* 2 (:counter @app-state)))) |
| 75 | + |
| 76 | +;; Eager tracking (starts immediately) |
| 77 | +(def eager-tracker (r/track! #(println "Counter changed:" (:counter @app-state)))) |
| 78 | + |
| 79 | +;; Cursor for nested state access |
| 80 | +(def counter-cursor (r/cursor app-state [:counter])) |
| 81 | + |
| 82 | +;; Wrap values with custom reset behavior |
| 83 | +(def wrapped-value (r/wrap (:counter @app-state) |
| 84 | + swap! app-state assoc :counter)) |
| 85 | + |
| 86 | +;; Component lifecycle with ratoms |
| 87 | +(defn with-local-state [] |
| 88 | + (r/with-let [local-state (r/atom 0)] |
| 89 | + [:div |
| 90 | + [:p "Local: " @local-state] |
| 91 | + [:button {:on-click #(swap! local-state inc)} "Increment"]])) |
| 92 | + |
| 93 | +;; Recursive swap for complex state updates |
| 94 | +(r/rswap! app-state update :counter inc) |
| 95 | +``` |
| 96 | + |
| 97 | +### React Interoperability |
| 98 | +```clojure |
| 99 | +;; Use React components in Reagent |
| 100 | +(def react-component (r/adapt-react-class js/SomeReactComponent)) |
| 101 | + |
| 102 | +;; Use Reagent components in React |
| 103 | +(def reagent-for-react (r/reactify-component my-reagent-component)) |
| 104 | + |
| 105 | +;; Create React elements directly |
| 106 | +(r/create-element "div" #js{:className "foo"} "Hello") |
| 107 | + |
| 108 | +;; Create React classes with full lifecycle control |
| 109 | +(def my-class |
| 110 | + (r/create-class |
| 111 | + {:component-did-mount (fn [this] (println "Mounted!")) |
| 112 | + :reagent-render (fn [] [:div "Custom class component"])})) |
| 113 | + |
| 114 | +;; Utility functions for props and styling |
| 115 | +(r/class-names "btn" "btn-primary" {:active true}) |
| 116 | +(r/merge-props {:class "base"} {:class "extra" :id "test"}) |
| 117 | + |
| 118 | +;; Safe HTML rendering |
| 119 | +(r/unsafe-html "<strong>Bold text</strong>") |
| 120 | +``` |
| 121 | + |
| 122 | +## Architecture Overview |
| 123 | + |
| 124 | +### Reactive System |
| 125 | +1. **RAtom**: Core reactive atom type that tracks dependencies |
| 126 | +2. **Track**: Computed values that automatically update when dependencies change |
| 127 | +3. **Batching**: Updates are batched and applied after render for performance |
| 128 | +4. **Dependency Graph**: Automatic tracking of which components depend on which atoms |
| 129 | + |
| 130 | +### Component Rendering |
| 131 | +1. **Template Compilation**: Hiccup vectors converted to React elements |
| 132 | +2. **Component Wrapping**: Reagent functions wrapped as React components |
| 133 | +3. **State Integration**: React state integrated with Reagent ratoms |
| 134 | +4. **Lifecycle Management**: React lifecycle methods available when needed |
| 135 | + |
| 136 | +### Update Flow |
| 137 | +``` |
| 138 | +Atom Change → Dependency Tracking → Batched Update → Component Re-render |
| 139 | +``` |
| 140 | + |
| 141 | +## Development Workflow |
| 142 | + |
| 143 | +### Setting Up Development |
| 144 | +```bash |
| 145 | +# Using Shadow CLJS (modern approach) |
| 146 | +npx shadow-cljs watch client |
| 147 | + |
| 148 | +# Using Babashka tasks |
| 149 | +bb shadow # Start Shadow CLJS watch mode |
| 150 | +bb build-report # Generate build analysis report |
| 151 | + |
| 152 | +# Running tests |
| 153 | +lein doo chrome-headless test auto |
| 154 | +npx shadow-cljs compile test # Shadow CLJS test compilation |
| 155 | +``` |
| 156 | + |
| 157 | +### REPL-Driven Development |
| 158 | +- Start REPL with `lein repl` or `clj -M:dev` |
| 159 | +- Connect to ClojureScript REPL for live coding |
| 160 | +- Hot-reloading with Figwheel automatically updates components when code changes |
| 161 | +- Reactive atoms automatically trigger re-renders when state changes |
| 162 | + |
| 163 | +### Building for Production |
| 164 | +```bash |
| 165 | +lein build # Creates optimized build |
| 166 | +lein build-examples # Builds example applications |
| 167 | +``` |
| 168 | + |
| 169 | +## Implementation Patterns |
| 170 | + |
| 171 | +### Component Design Patterns |
| 172 | +1. **Pure Functions**: Components as pure functions of props and state |
| 173 | +2. **Reactive State**: Use ratoms for local and global state |
| 174 | +3. **Props Destructuring**: `(defn my-comp [{:keys [title content]}] ...)` |
| 175 | +4. **Conditional Rendering**: Use `when`, `if`, and `case` directly in hiccup |
| 176 | + |
| 177 | +### State Management Patterns |
| 178 | +1. **Global App State**: Single ratom with nested maps |
| 179 | +2. **Cursors**: For accessing nested state without re-rendering parent |
| 180 | +3. **Computed Values**: Use `track` for derived state |
| 181 | +4. **Local Component State**: Use `r/with-let` for component-local atoms |
| 182 | + |
| 183 | +### Performance Optimization |
| 184 | +1. **Batched Updates**: Updates automatically batched by Reagent with `flush` and `after-render` control |
| 185 | +2. **Minimal Re-renders**: Only components using changed atoms re-render |
| 186 | +3. **React Keys**: Use `:key` metadata for list items |
| 187 | +4. **shouldComponentUpdate**: Use `React.memo` equivalent with `r/create-class` |
| 188 | +5. **Manual Rendering Control**: Use `r/flush` to force immediate renders, `r/next-tick` and `r/after-render` for timing control |
| 189 | +6. **Compiler Customization**: Create custom compilers with `r/create-compiler` for specialized rendering behavior |
| 190 | + |
| 191 | +## Extension Points |
| 192 | + |
| 193 | +### Custom Components |
| 194 | +- Extend with React lifecycle methods using `r/create-class` |
| 195 | +- Create reusable component libraries |
| 196 | +- Build higher-order components |
| 197 | + |
| 198 | +### State Management Extensions |
| 199 | +- Custom ratom-like types implementing `IReactiveAtom` |
| 200 | +- Middleware for state changes and debugging |
| 201 | +- Integration with external state management (Redux, etc.) |
| 202 | + |
| 203 | +### React Ecosystem Integration |
| 204 | +- Wrap third-party React components with `r/adapt-react-class` |
| 205 | +- Export Reagent components for React with `r/reactify-component` |
| 206 | +- Use React hooks through interop |
| 207 | + |
| 208 | +### Development Tools |
| 209 | +- Custom debugging and inspection tools |
| 210 | +- Development-only components and utilities |
| 211 | +- Performance monitoring and profiling extensions |
| 212 | + |
| 213 | +## Testing Strategy |
| 214 | +- Unit tests in `test/` directory using ClojureScript test framework |
| 215 | +- Component testing with simulated user interactions |
| 216 | +- Integration tests with full application state |
| 217 | +- Use `doo` for automated browser testing |
| 218 | + |
| 219 | +## Common Gotchas |
| 220 | +1. **Deref in Render**: Always deref atoms (`@atom`) in component render functions |
| 221 | +2. **Event Handlers**: Use anonymous functions `#(...)` or `partial` for event handlers |
| 222 | +3. **Keys for Lists**: Always provide `:key` metadata for dynamic lists |
| 223 | +4. **State Updates**: Use `swap!` or `reset!`, never mutate ratom contents directly |
| 224 | +5. **Component Names**: Use kebab-case for component function names |
0 commit comments