At my workplace I was being tasked with creating a mock chat store for internal local dev work, and while doing so I made few notes about Vue (I had some experience, but not with hooks), So this is just my obsidian notes, I hope its useful to you :)
Table of Contents
- Ref and Reactive References
- Watch and Reactivity
- Pinia Store Integration
- Practical Examples
- Best Practices
- Common Gotchas
Ref and Reactive References
What is Ref?
ref is Vue's way of making primitive values reactive. It wraps the value in a reactive object with a .value property.
import { ref } from 'vue'
// Inside Pinia Store
export const useMyStore = defineStore('my-store', () => {
// Creates a reactive reference
const count = ref<number>(0)
// To access or modify:
function increment() {
count.value++ // Need .value for refs
}
return {
count, // When exposed, components can use it without .value
increment
}
})
Types of Refs in Stores
// Simple ref
const isLoading = ref<boolean>(false)
// Array ref
const messages = ref<Message[]>([])
// Complex object ref
const currentUser = ref<User | null>(null)
// Ref with undefined
const selectedId = ref<string | undefined>(undefined)
Watch and Reactivity
Basic Watch Usage
import { watch, ref } from 'vue'
export const useMyStore = defineStore('my-store', () => {
const messages = ref<Message[]>([])
// Simple watch
watch(messages, (newMessages, oldMessages) => {
console.log('Messages changed:', newMessages)
})
})
Watch Options
// Immediate execution
watch(messages, (newMessages) => {
// This runs immediately and on changes
}, { immediate: true })
// Deep watching
watch(messages, (newMessages) => {
// Detects deep object changes
}, { deep: true })
// Multiple sources
watch(
[messages, selectedId],
([newMessages, newId], [oldMessages, oldId]) => {
// Triggers when either changes
}
)
Pinia Store Integration
Store Structure with Refs
export const useMyStore = defineStore('my-store', () => {
// State
const items = ref<Item[]>([])
const isLoading = ref(false)
const error = ref<Error | null>(null)
// Computed
const itemCount = computed(() => items.value.length)
// Actions
const fetchItems = async () => {
isLoading.value = true
try {
items.value = await api.getItems()
} catch (e) {
error.value = e as Error
} finally {
isLoading.value = false
}
}
return {
items,
isLoading,
error,
itemCount,
fetchItems
}
})
Composing Stores
export const useMainStore = defineStore('main-store', () => {
// Using another store
const otherStore = useOtherStore()
// Watching other store's state
watch(
() => otherStore.someState,
(newValue) => {
// React to other store's changes
}
)
})
Practical Examples
Auto-refresh Implementation
export const useChatStore = defineStore('chat-store', () => {
const messages = ref<Message[]>([])
const refreshInterval = ref<number | null>(null)
const isRefreshing = ref(false)
// Watch for auto-refresh state
watch(isRefreshing, (shouldRefresh) => {
if (shouldRefresh) {
startAutoRefresh()
} else {
stopAutoRefresh()
}
})
const startAutoRefresh = () => {
refreshInterval.value = window.setInterval(() => {
fetchNewMessages()
}, 5000)
}
const stopAutoRefresh = () => {
if (refreshInterval.value) {
clearInterval(refreshInterval.value)
refreshInterval.value = null
}
}
return {
messages,
isRefreshing,
startAutoRefresh,
stopAutoRefresh
}
})
Loading State Management
export const useDataStore = defineStore('data-store', () => {
const data = ref<Data[]>([])
const isLoading = ref(false)
const error = ref<Error | null>(null)
// Watch loading state for side effects
watch(isLoading, (loading) => {
if (loading) {
// Show loading indicator
} else {
// Hide loading indicator
}
})
// Watch for errors
watch(error, (newError) => {
if (newError) {
// Handle error (show notification, etc.)
}
})
})
Best Practices
1. Ref Initialisation
// ❌ Bad
const data = ref() // Type is 'any'
// ✅ Good
const data = ref<string[]>([]) // Explicitly typed
2. Watch Cleanup
// ❌ Bad - No cleanup
watch(source, () => {
const timer = setInterval(() => {}, 1000)
})
// ✅ Good - With cleanup
watch(source, () => {
const timer = setInterval(() => {}, 1000)
return () => clearInterval(timer) // Cleanup function
})
3. Computed vs Watch
// ❌ Bad - Using watch for derived state
watch(items, (newItems) => {
itemCount.value = newItems.length
})
// ✅ Good - Using computed for derived state
const itemCount = computed(() => items.value.length)
4. Store Organization
// ✅ Good store organization
export const useStore = defineStore('store', () => {
// State refs
const data = ref<Data[]>([])
const isLoading = ref(false)
// Computed properties
const isEmpty = computed(() => data.value.length === 0)
// Watchers
watch(data, () => {
// Handle data changes
})
// Actions
const fetchData = async () => {
// Implementation
}
// Return public interface
return {
data,
isLoading,
isEmpty,
fetchData
}
})
Common Gotchas
-
Forgetting
.value
// ❌ Bad
const count = ref(0)
count++ // Won't work
// ✅ Good
count.value++
- Watch Timing
// ❌ Bad - Might miss initial state
watch(source, () => {})
// ✅ Good - Catches initial state
watch(source, () => {}, { immediate: true })
- Memory Leaks
// ❌ Bad - No cleanup
const store = useStore()
setInterval(() => {
store.refresh()
}, 1000)
// ✅ Good - With cleanup
const intervalId = setInterval(() => {
store.refresh()
}, 1000)
onBeforeUnmount(() => clearInterval(intervalId))
Remember: Always consider cleanup, type safety, and proper organization when working with refs and watches in Pinia stores
Top comments (0)