Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Async data in nested component #32

Closed
Xowap opened this issue Dec 2, 2016 · 15 comments
Closed

Async data in nested component #32

Xowap opened this issue Dec 2, 2016 · 15 comments

Comments

@Xowap
Copy link

Xowap commented Dec 2, 2016

Hi,

It seems that loading async data is pretty easy from pages, yet I don't find any way to load data asynchronously from nested components.

To explain what I try to do, I made some example project: https://github.com/Xowap/nuxttest

You'll see that data loaded from index.vue is correctly rendered server-side yet data in HttpBin.vue isn't displayed at all and triggers an error.

Is that an anti-pattern? A feature to come?

Thanks,
Rémy

This question is available on Nuxt.js community (#c24)
@Atinux
Copy link
Member

Atinux commented Dec 2, 2016

Actually it is not possible because it's not linked to a route, Nuxt.js surcharges the component data() associated to a route to allow async data.

For sub components, there are 2 ways of achieving it:

  1. Making the API call in the mounted() hook and setting the data afterwards, downside: no server rendering
  2. Making the API call in the data() of the page component and giving the data as a prop to the subComponent: server rendering OK. But the data() of the page might be less readable because it's loading the async data of the sub components

It all depends if you want the sub components to be server-rendered or not.

I hope it solves your issue :)

@Xowap
Copy link
Author

Xowap commented Dec 2, 2016

This is kind of what I feared.

I've been reading Vue 2's documentation, it looks like Vuex becomes basically mandatory. Which also solves this problem. I guess the real clean solution is to use Vuex, right?

@Atinux
Copy link
Member

Atinux commented Dec 2, 2016

Yes of course I forgot about vuex, you can integrate the store easily with Nuxt.js, just add a store/index.js (look at examples/vuex-store)

@danieloprado
Copy link

danieloprado commented Jan 13, 2017

@Xowap Can you provide an example how implement it? thanks 😄

@visualcookie
Copy link

An example would really help.

@Etheryte
Copy link

@Atinux Could you elaborate why this is not possible? All the components should be known beforehand statically and it should be possible to make the appropriate calls with the given context. I'm looking into how to solve the issue myself, too, as it would tremendously improve modularization.

@Atinux
Copy link
Member

Atinux commented Oct 17, 2017

Hi @Etheryte

Sadly, there is no way to know the component three inside lib/app/server.js, I can only get the routes components and update them by calling the asyncData & fetch methods. I tried to find a way for it but it seems impossible right now.

But it's also a good thing to not call asyncData for "normal" components since it let's everyone create Vue component compatible with the same behaviour under nuxt.js and a basic vue app.

@pea3nut
Copy link

pea3nut commented Mar 24, 2018

Use this function. That will test all child component .fetch and call it.

function fetchDeep(component){
    var originFn =component.asyncData;
    component.asyncData =async function (ctx) {
        if(component.components){
            let childComponents =Object.values(component.components);
            console.log(childComponents);
            while(childComponents.length){
                let comp =childComponents.shift();
                if(comp.options.fetch) await comp.options.fetch(ctx);
                if(comp.components) childComponents.push(...Object.values(comp.components));
            }
        };

        return originFn(ctx);
    };
    return component;
};

There is an example:

A nav component.

import Vue from "vue"
import {mapGetters} from "vuex"
import Component from 'nuxt-class-component';

@Component({
    name :'blog-nav',
    async fetch({store}){
        await store.dispatch('fetchCategory');
    },
    computed :{
        ...mapGetters(['categoryMap']),
    },
})
export default class extends Vue {
    showCategory =['technology','works','think'];
}

A page component which import nav component.

import Vue from "vue"
import Component from 'nuxt-class-component';
import {ArticleInfo} from "@foxzilla/fireblog";
import Marked from 'marked';
import Nav from '~/components/blog-nav.vue';
import {fetchDeep} from '~/modules/jslib';


@Component(fetchDeep({ // ❗️❗️❗️
    async asyncData(ctx){
        var articleInfo:ArticleInfo =await ctx.app.$axios.$get(`/article/detail/${ctx.params.id}`);
        await (<any>Nav).options.fetch(ctx);
        return {articleInfo}
    },
    head(val:ArticleInfo){
        return {
            title: this.articleInfo.title,
        };
    },
    components :{
        'blog-nav' :Nav,
    },
}))
export default class extends Vue {
    articleInfo:ArticleInfo;
    md:(mdContent:string)=>string =Marked;
}

@pea3nut
Copy link

pea3nut commented Mar 24, 2018

Use this function. That will test all child component .fetch and call it.

export function fetchDeep(component){
    var originFn =component.asyncData;
    var fetchedComp =[];
    component.asyncData =async function (ctx) {
        if(component.components){
            let childComponents =Object.values(component.components);
            while(childComponents.length){
                let comp =childComponents.shift();
                if(fetchedComp.includes(comp))continue;
                else fetchedComp.push(comp);
                if(comp.options && comp.options.fetch) await comp.options.fetch(ctx);
                if(comp.fetch) await comp.fetch(ctx);
                if(comp.components) childComponents.push(...Object.values(comp.components));
                if(comp.options && comp.options.components) childComponents.push(...Object.values(comp.options.components));
            }
        };

        return originFn && originFn(ctx);
    };
    return component;
};

There is an example:

A nav component.

import Vue from "vue"
import {mapGetters} from "vuex"
import Component from 'nuxt-class-component';

@Component({
    name :'blog-nav',
    async fetch({store}){ // will be called
        await store.dispatch('fetchCategory');// use vuex
    },
    computed :{
        ...mapGetters(['categoryMap']),
    },
})
export default class extends Vue {
    showCategory =['technology','works','think'];
}

A page component which import nav component.

import Vue from "vue"
import Component from 'nuxt-class-component';
import {ArticleInfo} from "@foxzilla/fireblog";
import Nav from '~/components/blog-nav.vue';
import {fetchDeep} from '~/modules/jslib';


@Component(fetchDeep({ // ❗️❗️❗️
    async asyncData(ctx){
        var articleInfo:ArticleInfo =await ctx.app.$axios.$get(`/article/detail/${ctx.params.id}`);
        await (<any>Nav).options.fetch(ctx);
        return {articleInfo}
    },
    components :{
        'blog-nav' :Nav,
    },
}))
export default class extends Vue {
}

@Etheryte
Copy link

@pea3nut Does this work with SSR as well? I tried a similar approach a few releases ago and had a ton of problems getting it to work on the server side.

@pea3nut
Copy link

pea3nut commented Mar 24, 2018

@Etheryte It doesn't have a strong test, but now it works on npm run dev very well, not only enter a URL but also passing in by <router-link></router-link>, and, I have tested npm run generate just now, it works too.

Of course, my app is not complex enough now, maybe some bug hidden is there.
`

@Romick2005
Copy link

I've updated your function with possibility to leave empty fetch or asyncData component property and also use both fetch or asyncData on chold components:

export const fetchDeep = function (component) {
  const originFn = component.asyncData;
  component.asyncData = async function (ctx) {
    if (component.components) {
      const promisesToResolve = [];
      let childComponents = Object.values(component.components);
      while (childComponents.length) {
        let comp = childComponents.pop();
        if (comp.asyncData) {
          promisesToResolve.push(comp.asyncData(ctx));
        }
        if (comp.fetch) {
          promisesToResolve.push(comp.fetch(ctx));
        }
        if (comp.components) {
          childComponents.push(...Object.values(comp.components));
        }
      }
      await Promise.all(promisesToResolve);
    };
    return (originFn && originFn(ctx)) || Promise.resolve();
  };
  return component;
};

Is it correct that collecting all promises to resolve in array and then call Promise.all theoretically would work faster that couple of await? On it doesn't matter on server side render? And is it correct assumption that component promise resolve order is unimportant in such case?

@pea3nut
Copy link

pea3nut commented Mar 30, 2018

@Romick2005 I found some bugs in my fetchDeep, and I guess you fetchDeep has there bugs too.

export function fetchDeep(component){
    var originFn =component.asyncData;
    var fetchedComp =[];
    component.asyncData =async function (ctx) {
        if(component.components){
            let childComponents =Object.values(component.components);
            while(childComponents.length){
                let comp =childComponents.shift();
                if(fetchedComp.includes(comp))continue;
                else fetchedComp.push(comp);
                if(comp.options && comp.options.fetch) await comp.options.fetch(ctx);
                if(comp.fetch) await comp.fetch(ctx);
                if(comp.components) childComponents.push(...Object.values(comp.components));
                if(comp.options && comp.options.components) childComponents.push(...Object.values(comp.options.components));
            }
        };

        return originFn && originFn(ctx);
    };
    return component;
};

It's a little ugly... and too hackly.

@pea3nut
Copy link

pea3nut commented Mar 30, 2018

And, I'm no idea of how to use the async data in the component of /layouts. Does anyone have some ideas?

@lock
Copy link

lock bot commented Nov 2, 2018

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@lock lock bot locked as resolved and limited conversation to collaborators Nov 2, 2018
@danielroe danielroe added the 2.x label Jan 18, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

8 participants