# Task: React 19 Node Modernization ## Overview Update all frontend visual nodes in `noodl-viewer-react` to take advantage of React 19 features, remove deprecated patterns, and prepare the infrastructure for future React 19-only features like View Transitions. **Priority:** High **Estimated Effort:** 16-24 hours **Branch:** `feature/react19-node-modernization` --- ## Background With the editor upgraded to React 19 and the runtime to React 18.3 (95% compatible), we have an opportunity to modernize our node infrastructure. This work removes technical debt, simplifies code, and prepares the foundation for React 19-exclusive features. ### React 19 Changes That Affect Nodes 1. **`ref` as a regular prop** - No more `forwardRef` wrapper needed 2. **Improved `useTransition`** - Can now handle async functions 3. **`useDeferredValue` with initial value** - New parameter for better loading states 4. **Native document metadata** - ``, `<meta>` render directly 5. **Better Suspense** - Works with more scenarios 6. **`use()` hook** - Read resources in render (promises, context) 7. **Form actions** - `useActionState`, `useFormStatus`, `useOptimistic` 8. **Cleaner cleanup** - Ref cleanup functions --- ## Phase 1: Infrastructure Updates ### 1.1 Update `createNodeFromReactComponent` Wrapper **File:** `packages/noodl-viewer-react/src/react-component-node.js` (or `.ts`) **Changes:** - Remove automatic `forwardRef` wrapping logic - Add support for `ref` as a standard prop - Add optional `useTransition` integration for state updates - Add optional `useDeferredValue` wrapper for specified inputs **New Options:** ```javascript createNodeFromReactComponent({ // ... existing options // NEW: React 19 options react19: { // Enable transition wrapping for specified inputs transitionInputs: ['items', 'filter'], // Enable deferred value for specified inputs deferredInputs: ['searchQuery'], // Enable form action support formActions: true, } }) ``` ### 1.2 Update Base Node Classes **Files:** - `packages/noodl-viewer-react/src/nodes/std-library/visual-base.js` - Any shared base classes for visual nodes **Changes:** - Remove `forwardRef` patterns - Update ref handling to use callback ref pattern - Add utility methods for transitions: - `this.startTransition(callback)` - wrap updates in transition - `this.getDeferredValue(inputName)` - get deferred version of input ### 1.3 Update TypeScript Definitions **Files:** - `packages/noodl-viewer-react/static/viewer/global.d.ts.keep` - Any relevant `.d.ts` files **Changes:** - Update component prop types to include `ref` as regular prop - Add types for new React 19 hooks - Update `Noodl` namespace types if needed --- ## Phase 2: Core Visual Nodes ### 2.1 Group Node **File:** `packages/noodl-viewer-react/src/nodes/std-library/group.js` **Current Issues:** - Likely uses `forwardRef` or class component with ref forwarding - May have legacy lifecycle patterns **Updates:** - Convert to functional component with `ref` as prop - Use `useEffect` cleanup returns properly - Add optional `useDeferredValue` for children rendering (large lists) **New Capabilities:** - `Defer Children` input (boolean) - uses `useDeferredValue` for smoother updates - `Is Updating` output - true when deferred update pending ### 2.2 Text Node **File:** `packages/noodl-viewer-react/src/nodes/std-library/text.js` **Updates:** - Remove `forwardRef` wrapper - Simplify ref handling ### 2.3 Image Node **File:** `packages/noodl-viewer-react/src/nodes/std-library/image.js` **Updates:** - Remove `forwardRef` wrapper - Add resource preloading hints for React 19's `preload()` API (future enhancement slot) ### 2.4 Video Node **File:** `packages/noodl-viewer-react/src/nodes/std-library/video.js` **Updates:** - Remove `forwardRef` wrapper - Ensure ref cleanup is proper ### 2.5 Circle Node **File:** `packages/noodl-viewer-react/src/nodes/std-library/circle.js` **Updates:** - Remove `forwardRef` wrapper ### 2.6 Icon Node **File:** `packages/noodl-viewer-react/src/nodes/std-library/icon.js` (or `net.noodl.visual.icon`) **Updates:** - Remove `forwardRef` wrapper --- ## Phase 3: UI Control Nodes ### 3.1 Button Node **File:** `packages/noodl-viewer-react/src/nodes/std-library/button.js` (or `net.noodl.controls.button`) **Updates:** - Remove `forwardRef` wrapper - Add form action support preparation: - `formAction` input (string) - for future form integration - `Is Pending` output - when used in form with pending action ### 3.2 Text Input Node **File:** `packages/noodl-viewer-react/src/nodes/std-library/textinput.js` **Updates:** - Remove `forwardRef` wrapper - Consider `useDeferredValue` for `onChange` value updates - Add form integration preparation **New Capabilities (Optional):** - `Defer Updates` input - delays `Value` output updates for performance - `Immediate Value` output - non-deferred value for UI feedback ### 3.3 Checkbox Node **File:** `packages/noodl-viewer-react/src/nodes/std-library/checkbox.js` **Updates:** - Remove `forwardRef` wrapper - Add optimistic update preparation (`useOptimistic` slot) ### 3.4 Radio Button / Radio Button Group **Files:** - `packages/noodl-viewer-react/src/nodes/std-library/radiobutton.js` - `packages/noodl-viewer-react/src/nodes/std-library/radiobuttongroup.js` **Updates:** - Remove `forwardRef` wrappers - Ensure proper group state management ### 3.5 Options/Dropdown Node **File:** `packages/noodl-viewer-react/src/nodes/std-library/options.js` **Updates:** - Remove `forwardRef` wrapper - Consider `useDeferredValue` for large option lists ### 3.6 Range/Slider Node **File:** `packages/noodl-viewer-react/src/nodes/std-library/range.js` **Updates:** - Remove `forwardRef` wrapper - `useDeferredValue` for value output (prevent render thrashing during drag) **New Capabilities:** - `Deferred Value` output - smoothed value for expensive downstream renders - `Immediate Value` output - raw value for UI display --- ## Phase 4: Navigation Nodes ### 4.1 Page Router / Router Node **File:** `packages/noodl-viewer-react/src/nodes/std-library/router.js` **Updates:** - Add `useTransition` wrapping for navigation - Prepare for View Transitions API integration **New Capabilities:** - `Is Transitioning` output - true during page transition - `Use Transition` input (boolean, default true) - wrap navigation in React transition ### 4.2 Router Navigate Node **File:** `packages/noodl-viewer-react/src/nodes/std-library/routernavigate.js` **Updates:** - Wrap navigation in `startTransition` **New Capabilities:** - `Is Pending` output - navigation in progress - `Transition Priority` input (enum: 'normal', 'urgent') - for future prioritization ### 4.3 Page Stack / Component Stack **File:** `packages/noodl-viewer-react/src/nodes/std-library/pagestack.js` **Updates:** - Add `useTransition` for push/pop operations **New Capabilities:** - `Is Transitioning` output - Prepare for animation coordination with View Transitions ### 4.4 Page Inputs Node **File:** `packages/noodl-viewer-react/src/nodes/std-library/pageinputs.js` **Updates:** - Standard cleanup, ensure no deprecated patterns ### 4.5 Popup Nodes **Files:** - `packages/noodl-viewer-react/src/nodes/std-library/showpopup.js` - `packages/noodl-viewer-react/src/nodes/std-library/closepopup.js` **Updates:** - Consider `useTransition` for popup show/hide --- ## Phase 5: Layout Nodes ### 5.1 Columns Node **File:** `packages/noodl-viewer-react/src/nodes/std-library/columns.js` **Updates:** - Remove `forwardRef` wrapper - Remove `React.cloneElement` if present (React 19 has better patterns) - Consider using CSS Grid native features ### 5.2 Repeater (For Each) Node **File:** `packages/noodl-viewer-react/src/nodes/std-library/foreach.js` **Critical Updates:** - Add `useDeferredValue` for items array - Add `useTransition` for item updates **New Capabilities:** - `Defer Updates` input (boolean) - uses deferred value for items - `Is Updating` output - true when deferred update pending - `Transition Updates` input (boolean) - wrap updates in transition **Why This Matters:** Large list updates currently cause jank. With these options: - User toggles `Defer Updates` → list updates don't block UI - `Is Updating` output → can show loading indicator ### 5.3 Component Children Node **File:** `packages/noodl-viewer-react/src/nodes/std-library/componentchildren.js` **Updates:** - Standard cleanup --- ## Phase 6: Data/Object Nodes ### 6.1 Component Object Node **File:** `packages/noodl-viewer-react/src/nodes/std-library/componentobject.js` **Updates:** - Consider context-based implementation for React 19 - `use(Context)` can now be called conditionally in React 19 ### 6.2 Parent Component Object Node **File:** Similar location **Updates:** - Same as Component Object --- ## Phase 7: SEO/Document Nodes (New Capability) ### 7.1 Update Page Node for Document Metadata **File:** `packages/noodl-viewer-react/src/nodes/std-library/page.js` **New Capabilities:** React 19 allows rendering `<title>`, `<meta>`, `<link>` directly in components and they hoist to `<head>`. **New Inputs:** - `Page Title` - renders `<title>` (already exists, but implementation changes) - `Meta Description` - renders `<meta name="description">` - `Meta Keywords` - renders `<meta name="keywords">` - `Canonical URL` - renders `<link rel="canonical">` - `OG Title` - renders `<meta property="og:title">` - `OG Description` - renders `<meta property="og:description">` - `OG Image` - renders `<meta property="og:image">` **Implementation:** ```jsx function PageComponent({ title, description, ogTitle, ...props }) { return ( <> {title && <title>{title}} {description && } {ogTitle && } {/* ... rest of component */} ); } ``` This replaces the hacky SSR string replacement currently in `packages/noodl-viewer-react/static/ssr/index.js`. --- ## Phase 8: Testing & Validation ### 8.1 Unit Tests **Update/Create Tests For:** - `createNodeFromReactComponent` with new options - Each updated node renders correctly - Ref forwarding works without `forwardRef` - Deferred values update correctly - Transitions wrap updates properly ### 8.2 Integration Tests - Page navigation with transitions - Repeater with large datasets - Form interactions with new patterns ### 8.3 Visual Regression Tests - Ensure no visual changes from modernization - Test all visual states (hover, pressed, disabled) - Test variants still work ### 8.4 Performance Benchmarks **Before/After Metrics:** - Repeater with 1000 items - render time - Page navigation - transition smoothness - Text input rapid typing - lag measurement --- ## File List Summary ### Infrastructure Files ``` packages/noodl-viewer-react/src/ ├── react-component-node.js # Main wrapper factory ├── nodes/std-library/ │ └── visual-base.js # Base class for visual nodes ``` ### Visual Element Nodes ``` packages/noodl-viewer-react/src/nodes/std-library/ ├── group.js ├── text.js ├── image.js ├── video.js ├── circle.js ├── icon.js (or net.noodl.visual.icon) ``` ### UI Control Nodes ``` packages/noodl-viewer-react/src/nodes/std-library/ ├── button.js (or net.noodl.controls.button) ├── textinput.js ├── checkbox.js ├── radiobutton.js ├── radiobuttongroup.js ├── options.js ├── range.js ``` ### Navigation Nodes ``` packages/noodl-viewer-react/src/nodes/std-library/ ├── router.js ├── routernavigate.js ├── pagestack.js ├── pageinputs.js ├── showpopup.js ├── closepopup.js ``` ### Layout Nodes ``` packages/noodl-viewer-react/src/nodes/std-library/ ├── columns.js ├── foreach.js ├── componentchildren.js ``` ### Data Nodes ``` packages/noodl-viewer-react/src/nodes/std-library/ ├── componentobject.js ├── parentcomponentobject.js ``` ### Page/SEO Nodes ``` packages/noodl-viewer-react/src/nodes/std-library/ ├── page.js ``` ### Type Definitions ``` packages/noodl-viewer-react/static/viewer/ ├── global.d.ts.keep ``` --- ## Implementation Order ### Week 1: Foundation 1. Update `createNodeFromReactComponent` infrastructure 2. Update base classes 3. Update Group node (most used, good test case) 4. Update Text node 5. Create test suite for modernized patterns ### Week 2: Controls & Navigation 6. Update all UI Control nodes (Button, TextInput, etc.) 7. Update Navigation nodes with transition support 8. Update Repeater with deferred value support 9. Test navigation flow end-to-end ### Week 3: Polish & New Features 10. Update remaining nodes (Columns, Component Object, etc.) 11. Add Page metadata support 12. Performance testing and optimization 13. Documentation updates --- ## Success Criteria ### Must Have - [ ] All nodes render correctly after updates - [ ] No `forwardRef` usage in visual nodes - [ ] All refs work correctly (DOM access, focus, etc.) - [ ] No breaking changes to existing projects - [ ] Tests pass ### Should Have - [ ] Repeater has `Defer Updates` option - [ ] Page Router has `Is Transitioning` output - [ ] Page node has SEO metadata inputs ### Nice to Have - [ ] Performance improvement measurable in benchmarks - [ ] Text Input deferred value option - [ ] Range slider deferred value option --- ## Migration Notes ### Backward Compatibility These changes should be **fully backward compatible**: - Existing projects continue to work unchanged - New features are opt-in via new inputs - No changes to how nodes are wired together ### Runtime Considerations Since runtime is React 18.3: - `useTransition` works (available since React 18) - `useDeferredValue` works (available since React 18) - `ref` as prop works (React 18.3 forward-ported this) - Native metadata hoisting does NOT work (React 19 only) - For runtime, metadata nodes will need polyfill/fallback **Strategy:** Build features for React 19 editor, provide graceful degradation for React 18.3 runtime. Eventually upgrade runtime to React 19. --- ## Code Examples ### Before: forwardRef Pattern ```javascript getReactComponent() { return React.forwardRef((props, ref) => { return
{props.children}
; }); } ``` ### After: ref as Prop Pattern ```javascript getReactComponent() { return function GroupComponent({ ref, style, children }) { return
{children}
; }; } ``` ### Adding Deferred Value Support ```javascript getReactComponent() { return function RepeaterComponent({ items, deferUpdates, onIsUpdating }) { const deferredItems = React.useDeferredValue(items); const isStale = items !== deferredItems; React.useEffect(() => { onIsUpdating?.(isStale); }, [isStale, onIsUpdating]); const itemsToRender = deferUpdates ? deferredItems : items; return (
{itemsToRender.map(item => /* render item */)}
); }; } ``` ### Adding Transition Support ```javascript getReactComponent() { return function RouterComponent({ onNavigate, onIsTransitioning }) { const [isPending, startTransition] = React.useTransition(); React.useEffect(() => { onIsTransitioning?.(isPending); }, [isPending, onIsTransitioning]); const handleNavigate = (target) => { startTransition(() => { onNavigate(target); }); }; // ... }; } ``` --- ## Questions for Implementation 1. **File locations:** Need to verify actual file paths in `noodl-viewer-react` - the paths above are educated guesses based on patterns. 2. **Runtime compatibility:** Should we add feature detection to gracefully degrade on React 18.3 runtime, or assume eventual runtime upgrade? 3. **New inputs/outputs:** Should new capabilities (like `Defer Updates`) be hidden by default and exposed via a "React 19 Features" toggle in project settings? 4. **Breaking changes policy:** If we find any patterns that would break (unlikely), what's the policy? Migration path vs versioning? --- ## Related Future Work This modernization enables but does not include: - **Magic Transition Node** - View Transitions API wrapper - **AI Component Node** - Generative UI with streaming - **Async Boundary Node** - Suspense wrapper with error boundaries - **Form Action Node** - React 19 form actions These will be separate tasks building on this foundation. # React 19 Node Modernization - Implementation Checklist Quick reference checklist for implementation. See full spec for details. --- ## Pre-Flight Checks - [ ] Verify React 19 is installed in editor package - [ ] Verify React 18.3 is installed in runtime package - [ ] Create feature branch: `feature/react19-node-modernization` - [ ] Locate all node files in `packages/noodl-viewer-react/src/nodes/` --- ## Phase 1: Infrastructure ### createNodeFromReactComponent - [ ] Find file: `packages/noodl-viewer-react/src/react-component-node.js` - [ ] Remove automatic forwardRef wrapping - [ ] Add `ref` prop passthrough to components - [ ] Add optional `react19.transitionInputs` config - [ ] Add optional `react19.deferredInputs` config - [ ] Test: Basic node still renders - [ ] Test: Ref forwarding works ### Base Classes - [ ] Find visual-base.js or equivalent - [ ] Add `this.startTransition()` utility method - [ ] Add `this.getDeferredValue()` utility method - [ ] Update TypeScript definitions if applicable --- ## Phase 2: Core Visual Nodes ### Group Node - [ ] Remove forwardRef - [ ] Use `ref` as regular prop - [ ] Test: Renders correctly - [ ] Test: Ref accessible for DOM manipulation - [ ] Optional: Add `Defer Children` input - [ ] Optional: Add `Is Updating` output ### Text Node - [ ] Remove forwardRef - [ ] Test: Renders correctly ### Image Node - [ ] Remove forwardRef - [ ] Test: Renders correctly ### Video Node - [ ] Remove forwardRef - [ ] Ensure proper ref cleanup - [ ] Test: Renders correctly ### Circle Node - [ ] Remove forwardRef - [ ] Test: Renders correctly ### Icon Node - [ ] Remove forwardRef - [ ] Test: Renders correctly --- ## Phase 3: UI Control Nodes ### Button Node - [ ] Remove forwardRef - [ ] Test: Click events work - [ ] Test: Visual states work (hover, pressed, disabled) - [ ] Optional: Add `Is Pending` output for forms ### Text Input Node - [ ] Remove forwardRef - [ ] Test: Value binding works - [ ] Test: Focus/blur events work - [ ] Optional: Add `Defer Updates` input - [ ] Optional: Add `Immediate Value` output ### Checkbox Node - [ ] Remove forwardRef - [ ] Test: Checked state works ### Radio Button Node - [ ] Remove forwardRef - [ ] Test: Selection works ### Radio Button Group Node - [ ] Remove forwardRef - [ ] Test: Group behavior works ### Options/Dropdown Node - [ ] Remove forwardRef - [ ] Test: Selection works - [ ] Optional: useDeferredValue for large option lists ### Range/Slider Node - [ ] Remove forwardRef - [ ] Test: Value updates work - [ ] Optional: Add `Deferred Value` output - [ ] Optional: Add `Immediate Value` output --- ## Phase 4: Navigation Nodes ### Router Node - [ ] Remove forwardRef if present - [ ] Add useTransition for navigation - [ ] Add `Is Transitioning` output - [ ] Test: Page navigation works - [ ] Test: Is Transitioning output fires correctly ### Router Navigate Node - [ ] Wrap navigation in startTransition - [ ] Add `Is Pending` output - [ ] Test: Navigation triggers correctly ### Page Stack Node - [ ] Add useTransition for push/pop - [ ] Add `Is Transitioning` output - [ ] Test: Stack operations work ### Page Inputs Node - [ ] Standard cleanup - [ ] Test: Parameters pass correctly ### Show Popup Node - [ ] Consider useTransition - [ ] Test: Popup shows/hides ### Close Popup Node - [ ] Standard cleanup - [ ] Test: Popup closes --- ## Phase 5: Layout Nodes ### Columns Node - [ ] Remove forwardRef - [ ] Remove React.cloneElement if present - [ ] Test: Column layout works ### Repeater (For Each) Node ⭐ HIGH VALUE - [ ] Remove forwardRef if present - [ ] Add useDeferredValue for items - [ ] Add useTransition for updates - [ ] Add `Defer Updates` input - [ ] Add `Is Updating` output - [ ] Add `Transition Updates` input - [ ] Test: Basic rendering works - [ ] Test: Large list performance improved - [ ] Test: Is Updating output fires correctly ### Component Children Node - [ ] Standard cleanup - [ ] Test: Children render correctly --- ## Phase 6: Data Nodes ### Component Object Node - [ ] Review implementation - [ ] Consider React 19 context patterns - [ ] Test: Object access works ### Parent Component Object Node - [ ] Same as Component Object - [ ] Test: Parent access works --- ## Phase 7: Page/SEO Node ⭐ HIGH VALUE ### Page Node - [ ] Add `Page Title` input → renders `` - [ ] Add `Meta Description` input → renders `<meta name="description">` - [ ] Add `Canonical URL` input → renders `<link rel="canonical">` - [ ] Add `OG Title` input → renders `<meta property="og:title">` - [ ] Add `OG Description` input - [ ] Add `OG Image` input - [ ] Test: Metadata renders in head - [ ] Test: SSR works correctly - [ ] Provide fallback for React 18.3 runtime --- ## Phase 8: Testing ### Unit Tests - [ ] createNodeFromReactComponent tests - [ ] Ref forwarding tests - [ ] Deferred value tests - [ ] Transition tests ### Integration Tests - [ ] Full navigation flow - [ ] Repeater with large data - [ ] Form interactions ### Visual Tests - [ ] All nodes render same as before - [ ] Visual states work - [ ] Variants work ### Performance Tests - [ ] Benchmark: Repeater 1000 items - [ ] Benchmark: Page navigation - [ ] Benchmark: Text input typing --- ## Final Steps - [ ] Update documentation - [ ] Update changelog - [ ] Create PR - [ ] Test in sample projects - [ ] Deploy to staging - [ ] User testing --- ## Quick Reference: Pattern Changes ### forwardRef Removal **Before:** ```jsx React.forwardRef((props, ref) => <div ref={ref} />) ``` **After:** ```jsx function Component({ ref, ...props }) { return <div ref={ref} /> } ``` ### Adding Deferred Value ```jsx function Component({ items, deferUpdates, onIsUpdating }) { const deferredItems = React.useDeferredValue(items); const isStale = items !== deferredItems; React.useEffect(() => { onIsUpdating?.(isStale); }, [isStale]); return /* render deferUpdates ? deferredItems : items */; } ``` ### Adding Transitions ```jsx function Component({ onNavigate, onIsPending }) { const [isPending, startTransition] = React.useTransition(); React.useEffect(() => { onIsPending?.(isPending); }, [isPending]); const handleNav = (target) => { startTransition(() => onNavigate(target)); }; } ``` ### Document Metadata (React 19) ```jsx function Page({ title, description }) { return ( <> {title && <title>{title}} {description && } {/* rest of page */} ); } ``` --- ## Notes - High value items marked with ⭐ - Start with infrastructure, then Group node as test case - Test frequently - small iterations - Keep backward compatibility - no breaking changes