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.

Revisions

  1. nash403 created this gist Jan 8, 2025.
    28 changes: 28 additions & 0 deletions Readme.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,28 @@
    # 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:

    ```js
    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.
    ```
    38 changes: 38 additions & 0 deletions watchCreateEffectScope.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,38 @@
    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;
    }
    46 changes: 46 additions & 0 deletions watchCreateEffectScopeAsync.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,46 @@
    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;
    }