Skip to content

Recognize Props Passed to router-view As Reactive #3417

@martinmckenna

Description

@martinmckenna

What problem does this feature solve?

Currently having an issue where I want to

  1. Create a nested router-view
  2. Do some API fetching in the parent
  3. Pass down data to all the children components
  4. Have the children re-render when the reactive values update

What I tried was this

/* my-view.vue */

<template>
  <router-view
    :data="data"
    :loading="loading"
    :error="error"
    :lastUpdated="lastUpdated"
  />
</template>

<script>
import { useAPIRequest } from '@/hooks/useAPIRequest';

export default {
  setup() {
   /* this will make the API request on mount and update the appropriate values on resolve/reject */
    const {
      data,
      loading,
      error,
      lastUpdated,
    } = useAPIRequest(
      () => Promise.resolve([]),
    );

    return {
      data,
      loading,
      error,
      lastUpdated,
    };
  },
};
</script>

<style lang="scss" scoped></style>
/* useAPIRequest.js */

import { ref } from '@vue/composition-api';
/**
 *
 * @param { () => Promise<any> } request
 * @param { any } initialData
 *
 * @returns {
 *  loading: boolean;
 *  error: any;
 *  lastUpdated: number;
 *  data: any;
 * }
 */
export const useAPIRequest = (
  request,
  initialData,
) => {
  const data = ref(initialData || null);
  const loading = ref(true);
  const error = ref(null);
  const lastUpdated = ref(0);

  const handleRequest = () => {
    if (!request) {
      return Promise.resolve();
    }

    if (lastUpdated.value === 0) {
      loading.value = true;
    }
    return request()
      .then((response) => {
        data.value = response;
        loading.value = false;
        lastUpdated.value = Date.now();
        error.value = null;
      })
      .catch((err) => {
        error.value = err;
        loading.value = false;
      });
  };

  handleRequest();

  return {
    data,
    loading,
    error,
    lastUpdated,
    update: handleRequest,
  };
};

export default useAPIRequest;

But this doesn't work because the components that are rendered in the nested routes have access to these values through attrs.data, attrs.loading, attrs.lastUpdated, etc. They are not reactive and the children components don't re-render when these values change

What ended up working for me was passing the values down as refs, like so

/* my-view.vue */

<template>
  <router-view
    :data="data"
    :loading="loading"
    :error="error"
    :lastUpdated="lastUpdated"
  />
</template>

<script>
import { computed } from '@vue/composition-api';
import { useAPIRequest } from '@/hooks/useAPIRequest';

export default {
  setup() {
    const {
      data,
      loading,
      error,
      lastUpdated,
    } = useAPIRequest(
      () => Promise.resolve([]),
    );

    return {
      data: computed(() => data),
      loading: computed(() => loading),
      error: computed(() => error),
      lastUpdated: computed(() => lastUpdated),
    };
  },
};
</script>

<style lang="scss" scoped></style>

But I'm assuming passing down refs is an anti-pattern and probably isn't recommended.

My ask is that I want the ability to pass down reactive values to <router-view /> children and have them re-render when these values change

What does the proposed API look like?

Possibly <router-view render="(Component => <Component myProp="something" />)" />

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions