Vue3/Inertia Failed to resolve component - vue.js

I have a application which is built with the VILT-stack (Vue, Inertia, Laravel, Tailwind). I have some components like cards which could be used everywhere in the application. Because I don't want to manually import these components every time I built a function that registers the components in certain directories:
/**
* Register components from the ./components and ./components/core
* directories. This way these components don't have to be imported
* manually every time.
*
* #param {Vue instance} app
*/
function registerGlobalComponents (app) {
const requireComponent = require.context(
'./components' || './components/core',
true,
/\.vue$/
);
for (const file of requireComponent.keys()) {
const componentConfig = requireComponent(file);
const name = file
.replace(/index.js/, '')
.replace(/^\.\//, '')
.replace(/\.\w+$/, '');
const componentName = upperFirst(camelCase(name));
app.component(componentName,
componentConfig.default || componentConfig);
}
}
The creation of the inertia app happens in the same file:
/**
* Create an Inertia app instance.
*/
createInertiaApp({
resolve: (name) => import(`./Pages/${name}`),
setup ({ el, app, props, plugin }) {
const vueApp = createApp({ render: () => h(app, props) });
/**
* Mixin for showing notifications.
*/
vueApp.mixin({
data: function () {
return {};
},
computed: {
pageNotifications () {
return this.$page.props.notification;
}
}
});
vueApp.use(plugin).mount(el);
registerGlobalComponents(vueApp);
}
});
Because my card component is in the /components/core directory I have to call the component in the template tag like this: <core-card> content </core-card>. Now my card is perfectly showing on the page as you can see in the image.
But somehow I get the following error:
[Vue warn]: Failed to resolve component: core-card
I get this warning for all my other components that are registered through this registerGlobalComponents() function. Why do I get this warning when my components are showing correctly and working fine?

The problem why I got this error was because I was mounting the app before I registered the components. This resulted in the components being shown in my application but still getting the warning that the component couldn't be found. By importing the components before the mount this problem was solved.
I previously imported the components this way:
createInertiaApp({
resolve: (name) => import(`./Pages/${name}`),
setup ({ el, app, props, plugin }) {
const vueApp = createApp({ render: () => h(app, props) });
/**
* Mixin for showing notifications.
*/
vueApp.mixin({
data: function () {
return {};
},
computed: {
pageNotifications () {
return this.$page.props.notification;
}
}
});
vueApp.use(plugin).mount(el);
registerGlobalComponents(vueApp);
}
});
And changed the order of calling plugin, registerGlobalComponents and the mount of the app like this:
vueApp.use(plugin);
registerGlobalComponents(vueApp);
vueApp.mount(el);
I was able to fix this problem thanks to Robert from the official Inertia Discord. If he wants to claim the bounty I will definitely award him the points.

Related

Testing custom hooks that are using next-i18next useTranslation hook

I have created a custom hook that returns the translated value using the useTranslation hook.
import { useTranslation } from "next-i18next";
export const useCustomHook = (data) => {
const {t, i18n: { language: locale }} = useTranslation();
const value = {
field: t("some.key.from.json.file", { arg: data.arg }),
field2: data.name,
field3: t("another.key", {
arg: data.arg2, count: 3
})
}
return value;
};
I want to create a unit test for this custom hook, but I can't get the useTranslation hook to work as it does when running the app itself. Further info my current setup is as follows:
1- I'm using Nextjs with next-i18next library.
2- No i18n provider to wrap the app, only using HOC from next-i18next to wrap _app.
3- I have 2 json files for locales.
Is there a way to allow the useTranslation hook to work and get the parsed value from the translation file? here's what I tried so far too:
1- mocking the useTranslation hook, but this returns the ("another.key") as is without the parsed value.
2- I tried to create a wrapper with i18n-next provider, but that didn't work too.
Here's my test file.
describe("useCustomHook()", () => {
it("Should return correctly mapped props", () => {
const { result } = renderHook(() =>
useCustomHook(mockData)
);
const data = result.current[0];
expect(data.field).toBe(mockData.field); // this returns ("some.key.from.json.file") it doesn't use the t function,
// ... //
});

How to add a component to VueJs3 app before mounting

I have set up chartApp as my application like so:
const chartApp = {
data(){
...
},
methods:{
async init() {
//await async menthod
}
}
}
const app = Vue.createApp(chartApp).mount('#app');
app.init();
Which works.
I can go into the browser console, type app. and see the init function
I'm wanting to register a component. I've tried to set the app like this:
const chartApp = {
data(){
...
},
methods:{
async init() {
//await async menthod
}
}
}
const app = Vue.createApp(chartApp);
app.component('my-component', {
template:'<div>hello</div>'
});
app.mount('#app');
app.init();
But I receive an error stating
app.init is not a function
this time when I look at my browser console, and type app. ...I see an option to mount / unmount, but not init function.
I thought, maybe the app had failed to mount, so I tried:
app.mount('#app')
in the console but received the following warning:
App has already been mounted.
How am I able to register a component please? VueJs3 version is 3.2.16.
In the 1st case you are calling your init method on the result of the mount function.
But in the second case on the result of createApp....which is a very different object
Do this:
const app = Vue.createApp(chartApp);
app.component('my-component', {
template:'<div>hello</div>'
});
const mountedApp = app.mount('#app');
mountedApp.init();

Getting 'undefined is not an object' error when trying to use MST in react native Ignite template

I am using Ignite template for react native. I have created a simple object model and store that looks like so:
export const TimeObject = types.model('TimeObject', {
id: types.identifier,
hour: types.string,
minutes: types.string,
amOrPm: types.enumeration(['AM', 'PM']),
timeStamp: types.number,
numOfDrugs: types.number,
});
export const TimeStore = types
.model('TimeStore', {
time: types.map(TimeObject),
})
.actions(self => ({
addTime(json) {
const ids = [];
json.forEach(timeJson => {
if (!timeJson.id) {
timeJson.id = uuid.v1();
}
ids.push(timeJson.id);
self.time.put(timeJson);
});
return ids;
},
}));
When I use this in a screen component:
const { timeStore } = useStores();
const timeId = timeStore.addTime([
{
hours,
mins,
amOrPm,
timeStamp: timeStamp.getTime(), //With correct values given for inputs
},
]);
I get the undefined error. I am not sure what I'm doing wrong. I am testing this on Storybook, is there a different procedure to import it there?
I was able to solve this by just changing the order of ToggleStorybook and RootStoreProvider in app.tsx so that it now looks like this:
<RootStoreProvider value={rootStore}>
<ToggleStorybook>
<SafeAreaProvider initialMetrics={initialWindowMetrics}>
<RootNavigator
ref={navigationRef}
initialState={initialNavigationState}
onStateChange={onNavigationStateChange}
/>
</SafeAreaProvider>
</ToggleStorybook>
</RootStoreProvider>
I think because Storybook was toggled it never went to the RootStore and was thus unable to access it inside Storybook components but this method works now.

How do I use Vue.use with dynamic imports?

Currently I have
import { SchedulePlugin } from "#syncfusion/ej2-vue-schedule";
Vue.use(SchedulePlugin);
I would like to change this to a dynamic import.
I've changed the import to:
const { SchedulePlugin } = () => import("#syncfusion/ej2-vue-schedule");
but have been unable to find the syntax for the corresponding changes I need to make to Vue.use.
What is the correct syntax to use?
The dynamic import syntax you have is for specifying async components, where Vue internally resolves them. For Vue plugins, you have to resolve the module yourself before passing it on to Vue.use(). The import() method returns a Promise, so you can await the result of the module loading in the context of an async function:
const loadPlugins = async () => {
const { SchedulePlugin } = await import("#syncfusion/ej2-vue-schedule")
Vue.use(SchedulePlugin)
}
loadPlugins()
Note the plugins should be loaded before the app to ensure the plugin's effects are available to the app:
loadPlugins().then(() => {
new Vue({
render: (h) => h(App)
}).$mount("#app")
})
demo

Vue test-utils how to test a router.push()

In my component , I have a method which will execute a router.push()
import router from "#/router";
// ...
export default {
// ...
methods: {
closeAlert: function() {
if (this.msgTypeContactForm == "success") {
router.push("/home");
} else {
return;
}
},
// ....
}
}
I want to test it...
I wrote the following specs..
it("should ... go to home page", async () => {
// given
const $route = {
name: "home"
},
options = {
...
mocks: {
$route
}
};
wrapper = mount(ContactForm, options);
const closeBtn = wrapper.find(".v-alert__dismissible");
closeBtn.trigger("click");
await wrapper.vm.$nextTick();
expect(alert.attributes().style).toBe("display: none;")
// router path '/home' to be called ?
});
1 - I get an error
console.error node_modules/#vue/test-utils/dist/vue-test-utils.js:15
[vue-test-utils]: could not overwrite property $route, this is usually caused by a plugin that has added the property asa read-only value
2 - How I should write the expect() to be sure that this /home route has been called
thanks for feedback
You are doing something that happens to work, but I believe is wrong, and also is causing you problems to test the router. You're importing the router in your component:
import router from "#/router";
Then calling its push right away:
router.push("/home");
I don't know how exactly you're installing the router, but usually you do something like:
new Vue({
router,
store,
i18n,
}).$mount('#app');
To install Vue plugins. I bet you're already doing this (in fact, is this mechanism that expose $route to your component). In the example, a vuex store and a reference to vue-i18n are also being installed.
This will expose a $router member in all your components. Instead of importing the router and calling its push directly, you could call it from this as $router:
this.$router.push("/home");
Now, thise makes testing easier, because you can pass a fake router to your component, when testing, via the mocks property, just as you're doing with $route already:
const push = jest.fn();
const $router = {
push: jest.fn(),
}
...
mocks: {
$route,
$router,
}
And then, in your test, you assert against push having been called:
expect(push).toHaveBeenCalledWith('/the-desired-path');
Assuming that you have setup the pre-requisities correctly and similar to this
Just use
it("should ... go to home page", async () => {
const $route = {
name: "home"
}
...
// router path '/home' to be called ?
expect(wrapper.vm.$route.name).toBe($route.name)
});