0

I have a problem when importing with defineAsyncComponent and using render function

Here is parent template

<template>
    <div id="Widget"></div>
    <button @click="addTest()">Add Test</button>
</template>

Here is test.vue

<template>
    <button>Click Me ah la</button>
    <p>
        {{$t(`test`)}}
    </p>
</template>

I use the following code to dynamically create component and render it into "Widget" element

const addTest = async () => {

    const widget = await defineAsyncComponent(() =>
        import(`./test.vue`)
    );

    const node = h(widget);
    const el = document.getElementById("Widget");
    render(node, el);
}

I get the error

Uncaught (in promise) TypeError: _ctx.$t is not a function",

$t works without dynamic import.

What am I missing?

I also got the following wranning

Extraneous non-props attributes (app, config, mixins, components, directives, provides, optionsCache, propsCache, emitsCache, filters, reload) were passed to component but could not be automatically inherited because component renders fragment or text root nodes.

6
  • The problem is render(), not dynamic import. render creates a new app instance that is unaware of current one and doesn't have the plugins installed, including i18n. It's rarely ever used in a properly designed app. Consider explaining your case, there may be an idiomatic way for what you're trying to do Commented May 9 at 6:58
  • Thank for your reply, how to do? Commented May 9 at 8:43
  • Consider updating the question so it reflects your case. If you say it's github.com/gridstack/gridstack.js/blob/master/demo/… then it's a different case from what you described because you're limited to how third-party lib works and "el" isn't a direct child of your parent component. Commented May 9 at 9:32
  • Possible duplicate of stackoverflow.com/questions/69488256/… Commented May 9 at 9:36
  • Thank for help. I fixed my issue when I saw the post. Commented May 10 at 17:13

1 Answer 1

1

Solution 1

The way you create your dynamic component the global context of your app is not shared. As a workaround you can use the globally scoped t function that comes from i18n.global where i18n is the instance returned by useI18n(). You will need to create the i18n instance in a separate file (not main.js) so you can export it and the global t function.

/i18n/index.js

import { createI18n } from 'vue-i18n';

export const i18n = createI18n({
  locale: 'ja',
  fallbackLocale: 'en',
  messages: {
    en: {
      test: 'test',
    },
    ja: {
      test: 'test-ja',
    },
  },
});

export const { t } = i18n.global;

main.js - install i18n exported from i18n/index.js

import { createApp } from 'vue';
import { i18n } from './i18n';
import App from './App.vue';

const app = createApp(App);
app.use(i18n);
app.mount('#app');

Vue component - use t exported from i18n/index.js

<template>
  <button>Click Me ah la</button>
  <p>
    {{ t('test') }}
  </p>
</template>

<script setup>
  import { t } from './i18n'
</script>

Note that this t function is not reactive by default. You will need another workaround for that if it's important for your app.

working Stackblitz demo

Solution 2

First solution I tried to retain as much of your original code as possible, assuming it is necessary for you to use h() and render() functions. However, as an alternative, if you use Vue's built-in <component> element it will automatically share the app's global context. This will allow you to use your test.vue component as is.

App.vue

<template>
  <component :is="widget" />
  <button @click="addTest()">Add Test</button>
</template>

<script setup>
import { defineAsyncComponent, shallowRef } from 'vue';

const widget = shallowRef();

const addTest = async () => {
  widget.value = await defineAsyncComponent(() => import('./test.vue'));
};
</script>

working Stackblitz demo

Sign up to request clarification or add additional context in comments.

5 Comments

Why dynamic import doesn't pass $t to child component?
@Eagle Did you define it in the component context? When you render dynamic components, these are two different contexts.
How to do it? I search dynamic import doesn't mention it?
I've updated the answer with a second solution. Basically, you use <component> element (provided by Vue) to automatically share app global context with your dynamic component. This way $t can be used normally.
Thank for update. But I cannot to use <component> because I used GridStack (github.com/gridstack/gridstack.js/blob/master/demo/…). It must use h and render function. Any idea?

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.