mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2026-02-05 09:45:17 -05:00
224 lines
4.2 KiB
Markdown
224 lines
4.2 KiB
Markdown
---
|
|
description: Convert a React hook from thrillwiki-87 to a Vue composable
|
|
---
|
|
|
|
# Migrate Hook Workflow
|
|
|
|
Convert a React hook from thrillwiki-87 to a Vue 3 composable for the Nuxt 4 project.
|
|
|
|
## Step 1: Locate Source Hook
|
|
|
|
Find the React hook in thrillwiki-87:
|
|
```bash
|
|
/Volumes/macminissd/Projects/thrillwiki-87/src/hooks/
|
|
```
|
|
|
|
Key hooks (80+ total):
|
|
- `useAuth.tsx` - Authentication state
|
|
- `useModerationQueue.ts` - Moderation logic (21KB)
|
|
- `useEntityVersions.ts` - Version history (14KB)
|
|
- `useSearch.tsx` - Search functionality
|
|
- `useUnitPreferences.ts` - Unit conversion
|
|
- `useProfile.tsx` - User profile
|
|
- `useLocations.ts` - Location data
|
|
- `useRideCreditFilters.ts` - Credit filtering
|
|
|
|
## Step 2: Analyze Hook Pattern
|
|
|
|
Extract the hook structure:
|
|
|
|
```tsx
|
|
export function useFeature(params: Params) {
|
|
// State
|
|
const [data, setData] = useState<Type>(null)
|
|
const [loading, setLoading] = useState(false)
|
|
const [error, setError] = useState<Error | null>(null)
|
|
|
|
// Effects
|
|
useEffect(() => {
|
|
fetchData()
|
|
}, [dependency])
|
|
|
|
// Actions
|
|
const doSomething = async () => {
|
|
setLoading(true)
|
|
try {
|
|
const result = await api.call()
|
|
setData(result)
|
|
} catch (e) {
|
|
setError(e)
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
return { data, loading, error, doSomething }
|
|
}
|
|
```
|
|
|
|
## Step 3: Convert to Composable
|
|
|
|
### Basic Structure
|
|
```tsx
|
|
// React
|
|
export function useFeature() {
|
|
const [value, setValue] = useState('')
|
|
return { value, setValue }
|
|
}
|
|
```
|
|
↓
|
|
```typescript
|
|
// Vue
|
|
export function useFeature() {
|
|
const value = ref('')
|
|
|
|
function setValue(newValue: string) {
|
|
value.value = newValue
|
|
}
|
|
|
|
return { value, setValue }
|
|
}
|
|
```
|
|
|
|
### State Conversions
|
|
```tsx
|
|
// React
|
|
const [count, setCount] = useState(0)
|
|
const [user, setUser] = useState<User | null>(null)
|
|
const [items, setItems] = useState<Item[]>([])
|
|
```
|
|
↓
|
|
```typescript
|
|
// Vue
|
|
const count = ref(0)
|
|
const user = ref<User | null>(null)
|
|
const items = ref<Item[]>([])
|
|
```
|
|
|
|
### Effect Conversions
|
|
```tsx
|
|
// React - Run on mount
|
|
useEffect(() => {
|
|
initialize()
|
|
}, [])
|
|
```
|
|
↓
|
|
```typescript
|
|
// Vue
|
|
onMounted(() => {
|
|
initialize()
|
|
})
|
|
```
|
|
|
|
```tsx
|
|
// React - Watch dependency
|
|
useEffect(() => {
|
|
fetchData(id)
|
|
}, [id])
|
|
```
|
|
↓
|
|
```typescript
|
|
// Vue
|
|
watch(() => id, (newId) => {
|
|
fetchData(newId)
|
|
}, { immediate: true })
|
|
```
|
|
|
|
### Supabase → Django API
|
|
```tsx
|
|
// React (Supabase)
|
|
const { data } = await supabase
|
|
.from('parks')
|
|
.select('*')
|
|
.eq('slug', slug)
|
|
.single()
|
|
```
|
|
↓
|
|
```typescript
|
|
// Vue (Django)
|
|
const api = useApi()
|
|
const { data } = await api<Park>(`/parks/${slug}/`)
|
|
```
|
|
|
|
## Step 4: Handle Complex Patterns
|
|
|
|
### useCallback → Plain Function
|
|
```tsx
|
|
// React
|
|
const memoizedFn = useCallback(() => {
|
|
doSomething(dep)
|
|
}, [dep])
|
|
```
|
|
↓
|
|
```typescript
|
|
// Vue - Usually no memo needed
|
|
function doSomething() {
|
|
// Vue's reactivity handles this
|
|
}
|
|
```
|
|
|
|
### useMemo → computed
|
|
```tsx
|
|
// React
|
|
const derived = useMemo(() => expensiveCalc(data), [data])
|
|
```
|
|
↓
|
|
```typescript
|
|
// Vue
|
|
const derived = computed(() => expensiveCalc(data.value))
|
|
```
|
|
|
|
### Custom Hook Composition
|
|
```tsx
|
|
// React
|
|
function useFeature() {
|
|
const auth = useAuth()
|
|
const { data } = useQuery(...)
|
|
// ...
|
|
}
|
|
```
|
|
↓
|
|
```typescript
|
|
// Vue
|
|
export function useFeature() {
|
|
const { user } = useAuth()
|
|
const api = useApi()
|
|
// ...
|
|
}
|
|
```
|
|
|
|
## Step 5: Target Location
|
|
|
|
Place composables in:
|
|
```
|
|
frontend/app/composables/
|
|
├── useApi.ts # Base API client
|
|
├── useAuth.ts # Authentication
|
|
├── useParksApi.ts # Parks API
|
|
├── useRidesApi.ts # Rides API
|
|
├── useModeration.ts # Moderation queue
|
|
└── use[Feature].ts # New composables
|
|
```
|
|
|
|
## Step 6: Verify Parity
|
|
|
|
- [ ] All returned values present
|
|
- [ ] All actions/methods work
|
|
- [ ] State updates correctly
|
|
- [ ] API calls translated
|
|
- [ ] Error handling maintained
|
|
- [ ] Loading states work
|
|
- [ ] TypeScript types correct
|
|
|
|
## Priority Hooks to Migrate
|
|
|
|
| Hook | Size | Complexity |
|
|
|------|------|------------|
|
|
| useModerationQueue.ts | 21KB | High |
|
|
| useEntityVersions.ts | 14KB | High |
|
|
| useAuth.tsx | 11KB | Medium |
|
|
| useAutoComplete.ts | 10KB | Medium |
|
|
| useRateLimitAlerts.ts | 10KB | Medium |
|
|
| useRideCreditFilters.ts | 9KB | Medium |
|
|
| useAdminSettings.ts | 9KB | Medium |
|