--- 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(null) const [loading, setLoading] = useState(false) const [error, setError] = useState(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(null) const [items, setItems] = useState([]) ``` ↓ ```typescript // Vue const count = ref(0) const user = ref(null) const items = ref([]) ``` ### 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(`/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 |