# 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 = `
Shipping Destination
Select destination...
`;
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 (
Shipping Destination
setFormData({
...formData,
shipping: {
...formData.shipping,
destination_id: e.target.value
}
})}
>
Select destination...
{destinations.map(dest => (
{dest.label}
))}
);
}
// 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 (
setFormData({
...formData,
shipping: { ...formData.shipping, destination_id: value }
})}
/>
);
}
addFilter('woonoow_order_form_after_shipping', (container, props) => {
const root = ReactDOM.createRoot(container);
root.render( );
return container;
});
```
```javascript
// vite.config.js
export default {
build: {
lib: {
entry: 'src/index.tsx',
formats: ['iife'],
name: 'RajaongkirAddon'
},
rollupOptions: {
external: ['react', 'react-dom'],
output: {
globals: {
react: 'window.WooNooW.React',
'react-dom': 'window.WooNooW.ReactDOM'
}
}
}
}
};
```
---
## Documentation for Addon Developers
### Quick Start Guide:
```markdown
# WooNooW Addon Development
## Level 1: Vanilla JS (Easiest)
No build process needed. Just use `window.WooNooW` API.
## Level 2: React with Build (Recommended)
1. Setup project:
npm init
npm install --save-dev vite @types/react
2. Configure vite.config.js (see example above)
3. Use WooNooW's React:
const { React } = window.WooNooW;
4. Build:
npm run build
5. Enqueue in WordPress:
wp_enqueue_script('my-addon', plugin_dir_url(__FILE__) . 'dist/addon.js', ['woonoow-admin'], '1.0.0', true);
```
---
## Summary
**Your concern was valid!** ✅
**Solution:**
1. ✅ Expose React on `window.WooNooW.React`
2. ✅ Expose common components on `window.WooNooW.components`
3. ✅ Addons can use vanilla JS (no build) or React (with build)
4. ✅ Addons don't bundle React (use ours)
5. ✅ Proper documentation for developers
**Result:**
- Simple addons: Vanilla JS, no build
- Advanced addons: React with build, external React
- Best of both worlds!