Skip to content

Instantly share code, notes, and snippets.

@nash403
Created January 8, 2025 14:22
Show Gist options
  • Save nash403/bb3c4f4eb62cda9535a335879c9c28a6 to your computer and use it in GitHub Desktop.
Save nash403/bb3c4f4eb62cda9535a335879c9c28a6 to your computer and use it in GitHub Desktop.
Dynamic composable (or effect scope) loading with Vue 3

Dynamic composable loading with Vue 3

Reactive watcher to load effect scopes dynamically depending on the watched value. Typically, the effect scope you load is a function that contains reactive logic in the same way you would have in a component setup function.

Example use:

const watchSource = ref(...) // anything
                        
const composableReturnState = watchCreateEffectScope(watchSource, (watchValue) => {
  
  /*
   * This function is like a watcher callback. It gets called with the new value everytime the `watchSource` changes.
   * You can execute whatever code you would normally execute in a component setup function. Everytime the `watchSource`
   * value changes, all effects loaded previously gets disposed, and the callback function gets re-executed,
   * loading any new effects.
   */

  if (watchValue) {
    return composable1();
  } else {
    return composable2();
  }
});

// `composableReturnState` is a ref containing the state of the loaded effect.
import { useAsyncState, tryOnScopeDispose } from '@vueuse/core';
import { watch, ref, effectScope } from 'vue';
export function watchCreateEffectScope(
watchSource,
effectScopeFn,
defaultEffectState = null,
{ immediate = true, ...options } = {}
) {
let effState = ref(null);
let effScope;
const dispose = () => {
effScope && effScope.stop();
effState.value = null;
};
watch(
watchSource,
(newWatchedValue) => {
try {
dispose(); // first, dispose any previously loaded effect
effScope = effectScope();
const composable = () => effectScopeFn(newWatchedValue);
effState.value = effScope.run(composable);
} catch (error) {
console.error('Failed to load composable:', error);
return defaultEffectState;
}
},
{ immediate, ...options }
);
tryOnScopeDispose(dispose);
return effState;
}
export function watchCreateEffectScopeAsync(
watchSource,
asyncEffectScopeFn,
defaultEffectState = null,
{ immediate = true, resetOnExecute = false, delay = 0, ...options } = {}
) {
let effState, effScope;
const dispose = () => {
effScope && effScope.stop();
effState = null;
};
const returnState = useAsyncState(
async (watchedValue) => {
try {
dispose(); // first, dispose any previously loaded effect
effScope = effectScope();
const composable = await asyncEffectScopeFn(watchedValue);
effState = effScope.run(() => composable());
return effState;
} catch (error) {
console.error('Failed to load composable:', error);
return defaultEffectState;
}
},
defaultEffectState,
{
resetOnExecute,
...options,
immediate: false, // Keep as is. Set the watcher's immediate option below instead.
}
);
watch(
watchSource,
(newWatchedValue) =>
returnState.execute(Number(delay) || 0, newWatchedValue),
{ immediate, ...options }
);
tryOnScopeDispose(dispose);
return returnState;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment