# Addon React Integration - How It Works ## The Question **"How can addon developers use React if we only ship built `app.js`?"** You're absolutely right to question this! Let me clarify the architecture. --- ## Current Misunderstanding **What I showed in examples:** ```tsx // This WON'T work for external addons! import { addonLoader, addFilter } from '@woonoow/hooks'; import { DestinationSearch } from './components/DestinationSearch'; addonLoader.register({ id: 'rajaongkir-bridge', init: () => { addFilter('woonoow_order_form_after_shipping', (content) => { return ; // ❌ Can't do this! }); } }); ``` **Problem:** External addons can't import React components because: 1. They don't have access to our build pipeline 2. They only get the compiled `app.js` 3. React is bundled, not exposed --- ## Solution: Three Integration Levels ### **Level 1: Vanilla JS/jQuery** (Basic) **For simple addons that just need to inject HTML/JS** ```javascript // addon-bridge.js (vanilla JS, no build needed) (function() { // Wait for WooNooW to load window.addEventListener('woonoow:loaded', function() { // Access WooNooW hooks window.WooNooW.addFilter('woonoow_order_form_after_shipping', function(container, formData) { // Inject HTML const div = document.createElement('div'); div.innerHTML = `
`; container.appendChild(div); // Add event listeners document.getElementById('rajaongkir-dest').addEventListener('change', function(e) { // Update WooNooW state window.WooNooW.updateFormData({ shipping: { ...formData.shipping, destination_id: e.target.value } }); }); return container; }); }); })(); ``` **Pros:** - ✅ No build process needed - ✅ Works immediately - ✅ Easy for PHP developers - ✅ No dependencies **Cons:** - ❌ No React benefits - ❌ Manual DOM manipulation - ❌ No type safety --- ### **Level 2: Exposed React Runtime** (Recommended) **WooNooW exposes React on window for addons to use** #### WooNooW Core Setup: ```typescript // admin-spa/src/main.tsx import React from 'react'; import ReactDOM from 'react-dom/client'; // Expose React for addons window.WooNooW = { React: React, ReactDOM: ReactDOM, hooks: { addFilter: addFilter, addAction: addAction, // ... other hooks }, components: { // Expose common components Button: Button, Input: Input, Select: Select, // ... other UI components } }; ``` #### Addon Development (with build): ```javascript // addon-bridge.js (built with Vite/Webpack) const { React, hooks, components } = window.WooNooW; const { addFilter } = hooks; const { Button, Select } = components; // Addon can now use React! function DestinationSearch({ value, onChange }) { const [destinations, setDestinations] = React.useState([]); const [loading, setLoading] = React.useState(false); React.useEffect(() => { // Fetch destinations fetch('/wp-json/rajaongkir/v1/destinations') .then(res => res.json()) .then(data => setDestinations(data)); }, []); return React.createElement('div', { className: 'rajaongkir-search' }, React.createElement('label', null, 'Shipping Destination'), React.createElement(Select, { value: value, onChange: onChange, options: destinations, loading: loading }) ); } // Register with WooNooW addFilter('woonoow_order_form_after_shipping', function(container, formData, setFormData) { const root = ReactDOM.createRoot(container); root.render( React.createElement(DestinationSearch, { value: formData.shipping?.destination_id, onChange: (value) => setFormData({ ...formData, shipping: { ...formData.shipping, destination_id: value } }) }) ); return container; }); ``` **Addon Build Setup:** ```javascript // vite.config.js export default { build: { lib: { entry: 'src/addon.js', name: 'RajaongkirBridge', fileName: 'addon' }, rollupOptions: { external: ['react', 'react-dom'], // Don't bundle React output: { globals: { react: 'window.WooNooW.React', 'react-dom': 'window.WooNooW.ReactDOM' } } } } }; ``` **Pros:** - ✅ Can use React - ✅ Access to WooNooW components - ✅ Better DX - ✅ Type safety (with TypeScript) **Cons:** - ❌ Requires build process - ❌ More complex setup --- ### **Level 3: Slot-Based Rendering** (Advanced) **WooNooW renders addon components via slots** #### WooNooW Core: ```typescript // OrderForm.tsx function OrderForm() { // ... form logic return (
{/* ... shipping fields ... */} {/* Slot for addons to inject */}
); } // AddonSlot.tsx function AddonSlot({ name, props }) { const slots = useAddonSlots(name); return ( <> {slots.map((slot, index) => (
{slot.component(props)}
))} ); } ``` #### Addon Registration (PHP): ```php // rajaongkir-bridge.php add_filter('woonoow/addon_slots', function($slots) { $slots['order_form_after_shipping'][] = [ 'id' => 'rajaongkir-destination', 'component' => 'RajaongkirDestination', // Component name 'script' => plugin_dir_url(__FILE__) . 'dist/addon.js', 'priority' => 10, ]; return $slots; }); ``` #### Addon Component (React with build): ```typescript // addon/src/DestinationSearch.tsx import React, { useState, useEffect } from 'react'; export function RajaongkirDestination({ formData, setFormData }) { const [destinations, setDestinations] = useState([]); useEffect(() => { fetch('/wp-json/rajaongkir/v1/destinations') .then(res => res.json()) .then(setDestinations); }, []); return (
); } // Export for WooNooW to load window.WooNooWAddons = window.WooNooWAddons || {}; window.WooNooWAddons.RajaongkirDestination = RajaongkirDestination; ``` **Pros:** - ✅ Full React support - ✅ Type safety - ✅ Modern DX - ✅ Proper component lifecycle **Cons:** - ❌ Most complex - ❌ Requires build process - ❌ More WooNooW core complexity --- ## Recommended Approach: Level 2 (Exposed React) ### Implementation in WooNooW Core: ```typescript // admin-spa/src/main.tsx import React from 'react'; import ReactDOM from 'react-dom/client'; import { QueryClient } from '@tanstack/react-query'; // UI Components import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Select } from '@/components/ui/select'; import { Label } from '@/components/ui/label'; // ... other components // Hooks import { addFilter, addAction, applyFilters, doAction } from '@/lib/hooks'; // Expose WooNooW API window.WooNooW = { // React runtime React: React, ReactDOM: ReactDOM, // Hooks system hooks: { addFilter, addAction, applyFilters, doAction, }, // UI Components (shadcn/ui) components: { Button, Input, Select, Label, // ... expose commonly used components }, // Utilities utils: { api: api, // API client toast: toast, // Toast notifications }, // Version version: '1.0.0', }; // Emit loaded event window.dispatchEvent(new CustomEvent('woonoow:loaded')); ``` ### Addon Developer Experience: #### Option 1: Vanilla JS (No Build) ```javascript // addon.js (function() { const { React, hooks, components } = window.WooNooW; const { addFilter } = hooks; const { Select } = components; addFilter('woonoow_order_form_after_shipping', function(container, props) { // Use React.createElement (no JSX) const element = React.createElement(Select, { label: 'Destination', options: [...], value: props.formData.shipping?.destination_id, onChange: (value) => props.setFormData({...}) }); const root = ReactDOM.createRoot(container); root.render(element); return container; }); })(); ``` #### Option 2: With Build (JSX Support) ```typescript // addon/src/index.tsx const { React, hooks, components } = window.WooNooW; const { addFilter } = hooks; const { Select } = components; function DestinationSearch({ formData, setFormData }) { return (