
useToggleScope
A composable for conditionally managing Vue effect scopes based on reactive boolean conditions with automatic cleanup.
Usage
The useToggleScope composable wraps Vue’s effectScope API to create and destroy reactive effect scopes based on a boolean condition. When the condition becomes true, a new scope is created and your callback runs. When false, the scope is stopped and all effects are cleaned up automatically.
<script setup>
import { useToggleScope } from '@vuetify/v0'
import { shallowRef, watch } from 'vue'
const isEnabled = shallowRef(false)
const data = shallowRef(0)
const { isActive } = useToggleScope(isEnabled, () => {
// This watch is only active when isEnabled is true
watch(data, (value) => {
console.log('Data changed:', value)
})
})
</script>
<template>
<div>
<button @click="isEnabled = !isEnabled">
{{ isEnabled ? 'Disable' : 'Enable' }} Watcher
</button>
<p>Scope active: {{ isActive }}</p>
<input v-model.number="data" type="number">
</div>
</template>useToggleScope
Type
interface ToggleScopeControls { isActive: Readonly<Ref<boolean>> stop: () => void start: () => void reset: () => void } function useToggleScope( source: WatchSource<boolean>, fn: (() => void) | ((controls: ToggleScopeControls) => void) ): ToggleScopeControlsDetails
Creates an effect scope that is conditionally active based on a reactive boolean source. All reactive effects created within the scope are automatically cleaned up when the scope stops.
Parameters
source: A reactive boolean value or getter that controls when the scope is activefn: Function to execute within the scope. Can optionally receive controls for manual scope management.
Returns
isActive: Whether the scope is currently active (created and running)stop(): Stop the scope (destroys and cleans up all effects)start(): Start the scope (creates and runs effects)reset(): Reset the scope (stops and immediately restarts)
Example
const isFeatureEnabled = ref(false) const { isActive, stop, start } = useToggleScope(isFeatureEnabled, () => { // All watchers and effects created here are automatically cleaned up // when isFeatureEnabled becomes false watch(someData, () => { console.log('Data changed') }) watchEffect(() => { console.log('Effect running') }) }) // Manually control the scope stop() // Stops even if isFeatureEnabled is true start() // Starts the scope
Use Cases
Feature Flags
Enable/disable features dynamically with proper cleanup:
const isDarkMode = shallowRef(false)
useToggleScope(isDarkMode, () => {
// Dark mode specific watchers and effects
watch(theme, (value) => {
document.documentElement.classList.toggle('dark', value.isDark)
})
// Auto-cleanup when dark mode is disabled
})Conditional API Polling
Start/stop polling based on component visibility:
const isVisible = shallowRef(true)
useToggleScope(isVisible, () => {
const intervalId = setInterval(() => {
fetchData()
}, 5000)
onScopeDispose(() => {
clearInterval(intervalId)
})
})Performance Optimization
Only run expensive reactive effects when needed:
const isExpanded = shallowRef(false)
useToggleScope(isExpanded, () => {
// Heavy computations only run when panel is expanded
watch([data1, data2, data3], () => {
performExpensiveCalculation()
})
})Debug Mode
Toggle debug logging without performance overhead:
const isDebugMode = shallowRef(false)
useToggleScope(isDebugMode, () => {
watch(() => store.state, (state) => {
console.log('State changed:', state)
}, { deep: true })
})Lifecycle & Cleanup
Automatic Cleanup
useToggleScope automatically cleans up the effect scope when:
- The source condition becomes false
- The parent component unmounts
- The parent Vue effect scope is disposed
Implementation:
// All effects are disposed when scope stops
scope.value?.stop()This ensures all watchers, computed properties, and effects created within the scope are properly cleaned up, preventing memory leaks.
Manual Control
The composable returns control functions for manual scope management:
const { isActive, start, stop, reset } = useToggleScope(isEnabled, () => {
// Scope logic
})
// Check if scope is active
console.log(isActive.value) // true/false
// Manually stop the scope
stop()
// Manually start the scope
start()
// Reset (stop and immediately restart)
reset()Using Controls Inside Scope
Pass controls to the scope function for self-management:
useToggleScope(isEnabled, (controls) => {
// Access scope state
console.log('Active:', controls.isActive.value)
// Self-reset on certain conditions
watch(errorCount, (count) => {
if (count > 10) {
controls.reset() // Restart the scope
}
})
})Advanced Patterns
Nested Scopes
Create hierarchical scope management:
const parentEnabled = shallowRef(true)
const childEnabled = shallowRef(true)
useToggleScope(parentEnabled, () => {
console.log('Parent scope active')
useToggleScope(childEnabled, () => {
console.log('Child scope active')
// Child automatically stops when parent stops
})
})Coordinated Scopes
Multiple scopes controlled by different conditions:
const isOnline = shallowRef(true)
const isAuthenticated = shallowRef(false)
// API polling only when online
useToggleScope(isOnline, () => {
setInterval(() => fetchData(), 5000)
})
// User-specific features only when authenticated
useToggleScope(isAuthenticated, () => {
watch(userData, syncToServer)
})Delayed Activation
Use computed source for complex conditions:
const isReady = shallowRef(false)
const hasData = shallowRef(false)
const shouldActivate = computed(() => isReady.value && hasData.value)
useToggleScope(shouldActivate, () => {
// Only runs when both conditions are true
})Scope with Cleanup Handler
Ensure proper resource cleanup:
useToggleScope(isActive, () => {
const ws = new WebSocket('wss://api.example.com')
ws.onmessage = (event) => {
handleMessage(event.data)
}
onScopeDispose(() => {
ws.close()
console.log('WebSocket closed')
})
})Best Practices
Keep scope functions pure:
// Good - self-contained
useToggleScope(isEnabled, () => {
const data = shallowRef(0)
watch(data, () => console.log(data.value))
})
// Avoid - external state modifications
let externalCount = 0
useToggleScope(isEnabled, () => {
externalCount++ // Don't mutate external state
})Use onScopeDispose for cleanup:
useToggleScope(isActive, () => {
const timerId = setInterval(() => {}, 1000)
// Always cleanup non-reactive resources
onScopeDispose(() => {
clearInterval(timerId)
})
})Prefer reactive sources over manual control:
// Good - reactive and automatic
const isEnabled = shallowRef(true)
useToggleScope(isEnabled, () => {})
// Less ideal - manual control
const { start, stop } = useToggleScope(() => false, () => {})
start() // Manual management requiredDon’t create scopes in loops:
// Bad - creates multiple scopes
items.forEach(item => {
useToggleScope(item.isActive, () => {})
})
// Good - single scope observing array
useToggleScope(hasActiveItems, () => {
items.value.forEach(item => {
if (item.isActive) setupItem(item)
})
})Performance Tips
Use for expensive operations only:
// Good - heavy operation worth toggling
useToggleScope(isExpanded, () => {
watch(massiveArray, computeExpensiveStats, { deep: true })
})
// Overkill - simple operation
useToggleScope(isVisible, () => {
const simple = computed(() => a.value + b.value)
})Batch related effects:
// Good - related effects in one scope
useToggleScope(isActive, () => {
watch(data1, handler1)
watch(data2, handler2)
watch(data3, handler3)
})
// Less efficient - separate scopes
useToggleScope(isActive, () => watch(data1, handler1))
useToggleScope(isActive, () => watch(data2, handler2))
useToggleScope(isActive, () => watch(data3, handler3))Debounce rapid toggles:
import { refDebounced } from '@vueuse/core'
const isEnabled = shallowRef(false)
const debouncedEnabled = refDebounced(isEnabled, 300)
useToggleScope(debouncedEnabled, () => {
// Won't thrash on rapid toggles
})