|
| 1 | +# Composables Development Guide |
| 2 | + |
| 3 | +This guide covers the development of composables in the Nuxt Users module, including important considerations for build-time compatibility and best practices. |
| 4 | + |
| 5 | +## Overview |
| 6 | + |
| 7 | +Composables in the Nuxt Users module provide reactive state management and API interactions. However, there are important considerations when developing composables that will be used in Vue components, especially regarding build-time execution. |
| 8 | + |
| 9 | +## Build-Time Compatibility Issues |
| 10 | + |
| 11 | +### The Problem |
| 12 | + |
| 13 | +When Vue SFC (Single File Component) components are transformed during the module build process, any composables called at the module level (outside of lifecycle hooks) are executed during build time. This can cause issues if the composable uses Nuxt-specific functions like `useState()` when Nuxt is not yet available. |
| 14 | + |
| 15 | +**Error Example:** |
| 16 | +``` |
| 17 | +[Vue Router warn]: uncaught error during route navigation: |
| 18 | +ERROR [nuxt] A composable that requires access to the Nuxt instance was called outside of a plugin, Nuxt hook, Nuxt middleware, or Vue setup function. |
| 19 | +``` |
| 20 | + |
| 21 | +### Root Cause |
| 22 | + |
| 23 | +The error occurs because: |
| 24 | +1. Vue SFC components are transformed during the module build process |
| 25 | +2. During this transformation, composables that use `useState()` or other Nuxt-specific functions are executed |
| 26 | +3. At build time, the Nuxt instance is not available, causing the error |
| 27 | + |
| 28 | +### Which Composables Are Affected |
| 29 | + |
| 30 | +**Composables that use `useState()` (need special handling):** |
| 31 | +- `useUsers()` - Uses `useState()` for global state management |
| 32 | +- `useAuthentication()` - Uses `useState()` for user state |
| 33 | + |
| 34 | +**Composables that are safe (no special handling needed):** |
| 35 | +- `usePasswordValidation()` - Only uses Vue's `ref()` and `computed()` |
| 36 | +- `usePublicPaths()` - Only uses `useRuntimeConfig()` (Nuxt-provided) |
| 37 | + |
| 38 | +**Nuxt-provided composables (always safe):** |
| 39 | +- `useRuntimeConfig()` - Designed to work during build process |
| 40 | +- `useRoute()` - Nuxt-provided |
| 41 | +- `useRouter()` - Nuxt-provided |
| 42 | + |
| 43 | +## Solution: Lazy Initialization Pattern |
| 44 | + |
| 45 | +For components that use composables with `useState()`, implement the lazy initialization pattern: |
| 46 | + |
| 47 | +### Before (Problematic) |
| 48 | +```vue |
| 49 | +<script setup lang="ts"> |
| 50 | +import { useUsers } from '../composables/useUsers' |
| 51 | +
|
| 52 | +// ❌ This causes build-time error |
| 53 | +const { users, pagination, loading, error, fetchUsers, removeUser } = useUsers() |
| 54 | +</script> |
| 55 | +``` |
| 56 | + |
| 57 | +### After (Fixed) |
| 58 | +```vue |
| 59 | +<script setup lang="ts"> |
| 60 | +import { onMounted, computed } from 'vue' |
| 61 | +import { useUsers } from '../composables/useUsers' |
| 62 | +
|
| 63 | +// Initialize the composable state as null initially |
| 64 | +let usersComposable: ReturnType<typeof useUsers> | null = null |
| 65 | +
|
| 66 | +// Create computed properties that safely access the composable |
| 67 | +const users = computed(() => usersComposable?.users.value ?? []) |
| 68 | +const pagination = computed(() => usersComposable?.pagination.value ?? null) |
| 69 | +const loading = computed(() => usersComposable?.loading.value ?? false) |
| 70 | +const error = computed(() => usersComposable?.error.value ?? null) |
| 71 | +
|
| 72 | +onMounted(() => { |
| 73 | + // Initialize the composable only after the component is mounted |
| 74 | + usersComposable = useUsers() |
| 75 | + |
| 76 | + // Fetch data if needed |
| 77 | + if (users.value.length === 0) { |
| 78 | + usersComposable.fetchUsers() |
| 79 | + } |
| 80 | +}) |
| 81 | +
|
| 82 | +const handleFetchUsers = (page?: number, limit?: number) => { |
| 83 | + if (usersComposable) { |
| 84 | + usersComposable.fetchUsers(page, limit) |
| 85 | + } |
| 86 | +} |
| 87 | +</script> |
| 88 | +``` |
| 89 | + |
| 90 | +## Implementation Guidelines |
| 91 | + |
| 92 | +### 1. Identify Affected Components |
| 93 | + |
| 94 | +Check which components use composables that call `useState()`: |
| 95 | + |
| 96 | +```bash |
| 97 | +grep -r "useUsers\|useAuthentication" src/runtime/components/ |
| 98 | +``` |
| 99 | + |
| 100 | +### 2. Apply the Pattern |
| 101 | + |
| 102 | +For each affected component: |
| 103 | +1. Move the composable call to `onMounted()` |
| 104 | +2. Create computed properties for reactive values |
| 105 | +3. Create wrapper functions for methods |
| 106 | +4. Add null checks where necessary |
| 107 | + |
| 108 | +### 3. Keep Nuxt-Provided Composables at Module Level |
| 109 | + |
| 110 | +Nuxt-provided composables like `useRuntimeConfig()` should remain at the module level: |
| 111 | + |
| 112 | +```vue |
| 113 | +<script setup lang="ts"> |
| 114 | +// ✅ Safe to keep at module level |
| 115 | +const { public: { nuxtUsers } } = useRuntimeConfig() |
| 116 | +
|
| 117 | +// ❌ Custom composable with useState needs lazy initialization |
| 118 | +let authComposable: ReturnType<typeof useAuthentication> | null = null |
| 119 | +
|
| 120 | +onMounted(() => { |
| 121 | + authComposable = useAuthentication() |
| 122 | +}) |
| 123 | +</script> |
| 124 | +``` |
| 125 | + |
| 126 | +## Testing the Fix |
| 127 | + |
| 128 | +After applying the fix, verify that: |
| 129 | + |
| 130 | +1. **Build succeeds** - No more build-time errors |
| 131 | +2. **Components work correctly** - All functionality preserved |
| 132 | +3. **Reactivity maintained** - Computed properties update properly |
| 133 | +4. **Unit tests pass** - All existing tests continue to work |
| 134 | + |
| 135 | +## Best Practices |
| 136 | + |
| 137 | +### When Developing New Composables |
| 138 | + |
| 139 | +1. **Prefer Vue primitives** - Use `ref()` and `computed()` when possible |
| 140 | +2. **Use `useState()` sparingly** - Only when you need global state |
| 141 | +3. **Document requirements** - Clearly indicate if a composable uses `useState()` |
| 142 | +4. **Test build compatibility** - Ensure composables work in both build and runtime |
| 143 | + |
| 144 | +### When Using Composables in Components |
| 145 | + |
| 146 | +1. **Check the source** - Look at what the composable uses internally |
| 147 | +2. **Apply lazy initialization** - For composables using `useState()` |
| 148 | +3. **Keep Nuxt composables at module level** - They're designed for it |
| 149 | +4. **Test thoroughly** - Ensure both build and runtime work correctly |
| 150 | + |
| 151 | +## Common Patterns |
| 152 | + |
| 153 | +### Pattern 1: Simple State Management |
| 154 | +```typescript |
| 155 | +// ✅ Safe - only uses Vue primitives |
| 156 | +export const useSimpleState = () => { |
| 157 | + const count = ref(0) |
| 158 | + const increment = () => count.value++ |
| 159 | + return { count, increment } |
| 160 | +} |
| 161 | +``` |
| 162 | + |
| 163 | +### Pattern 2: Global State with useState |
| 164 | +```typescript |
| 165 | +// ⚠️ Requires lazy initialization in components |
| 166 | +export const useGlobalState = () => { |
| 167 | + const state = useState('global', () => ({ count: 0 })) |
| 168 | + const increment = () => state.value.count++ |
| 169 | + return { state, increment } |
| 170 | +} |
| 171 | +``` |
| 172 | + |
| 173 | +### Pattern 3: Mixed Usage |
| 174 | +```typescript |
| 175 | +// ⚠️ Requires lazy initialization due to useState |
| 176 | +export const useMixedState = () => { |
| 177 | + const globalState = useState('global', () => ({ count: 0 })) |
| 178 | + const localState = ref({ loading: false }) |
| 179 | + |
| 180 | + return { globalState, localState } |
| 181 | +} |
| 182 | +``` |
| 183 | + |
| 184 | +## Troubleshooting |
| 185 | + |
| 186 | +### Build Errors |
| 187 | +If you encounter build-time errors: |
| 188 | +1. Check which composables are being called at module level |
| 189 | +2. Identify which ones use `useState()` |
| 190 | +3. Apply the lazy initialization pattern |
| 191 | +4. Test the build again |
| 192 | + |
| 193 | +### Runtime Errors |
| 194 | +If components don't work after the fix: |
| 195 | +1. Ensure computed properties are properly set up |
| 196 | +2. Check that null checks are in place |
| 197 | +3. Verify that methods are properly wrapped |
| 198 | +4. Test the component functionality |
| 199 | + |
| 200 | +## Related Documentation |
| 201 | + |
| 202 | +- [Architecture Guide](./architecture.md) - Overall module architecture |
| 203 | +- [Code Style Guide](./code-style.md) - Coding conventions |
| 204 | +- [Testing Guide](./testing.md) - Testing strategies |
| 205 | +- [User Guide - Composables](../user-guide/composables.md) - How to use composables |
0 commit comments