Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

coryarobbins's avatar

Inertia.put Results in this.resolveComponent is not a function Error

Hello All,

I have a project where I need to have the AgendaTopics.vue display the Topics/Show.vue page with the AgendaTopic.vue component within a modal box. This is working as intended at the moment. My issue is that I need for the updateTopic method within the AgendaTopic.vue to reload within the modal box instead of reloading the entire page. I'll admit that I'm totally new to vue and inertia. I've attempted to use various A.I. to help solve the issue, but I'm getting "Uncaught (in promise) TypeError: this.resolveComponent is not a function" when I try to issue the Inertia.put portion of the script. I would greatly appreciate any help available.

TopicController update method:

    public function update(Request $request, Topic $topic)
    {
        $data = $request->all();

        // Update topic fields
        $topic->update([
            'topic' => $request->input('topic'),
            'description' => $request->input('description'),
            'private_description' => $request->input('private_description'),
            'is_private' => $request->input('is_private'),
        ]);

        // Clear existing subtopics and re-save
        $topic->subtopic()->delete();
        foreach ($request->input('subTopics') as $subTopicData) {
            $subTopic = $topic->subtopic()->create([
                'subtopic' => $subTopicData['subtopic'],
                'topic_id' => $topic->id,
            ]);

            foreach ($subTopicData['subSubTopics'] as $subSubTopicData) {
                $subTopic->subSubTopics()->create([
                    'subSubTopic' => $subSubTopicData['subSubTopic'],
                    'sub_topic_id' => $subTopic->id,
                ]);
            }
        }

        // Update links
        $topic->link()->where('is_private', 0)->delete(); // Remove existing public links
        $topic->link()->where('is_private', 1)->delete(); // Remove existing private links
        foreach ($data['links'] as $link) {
            $topic->link()->create([
                'link' => $link['link'],
                'is_private' => 0,
                'topic_id' => $topic->id,
            ]);
        }
        foreach ($data['privlinks'] as $link) {
            $topic->link()->create([
                'link' => $link['link'],
                'is_private' => 1,
                'topic_id' => $topic->id,
            ]);
        }

        // Update attachments
        $topic->attachment()->where('is_private', 0)->delete(); // Remove existing public attachments
        $topic->attachment()->where('is_private', 1)->delete(); // Remove existing private attachments
        foreach ($data['attachments'] as $attachment) {
            $topic->attachment()->create([
                'attachment' => $attachment['attachment'],
                'is_private' => 0,
                'topic_id' => $topic->id,
            ]);
        }
        foreach ($data['privAttachments'] as $attachment) {
            $topic->attachment()->create([
                'attachment' => $attachment['attachment'],
                'is_private' => 1,
                'topic_id' => $topic->id,
            ]);
        }

        // Return Inertia response
        return Inertia::render('Topics/Show', [
            'topic' => new TopicResource($topic->load(['subtopic.subSubTopics', 'agenda.meeting.team'])),
            'written' => 2,
        ]);
    }

AgendaTopics.vue:

<script lang="ts" setup>
import { ref } from 'vue';
import { Link, useForm } from "@inertiajs/vue3";
import { VueDraggable } from 'vue-draggable-plus';
import { SortableEvent } from "sortablejs";
import TopicsShow from '@/Pages/Topics/Show.vue';
import { Inertia } from "@inertiajs/inertia";

const props = defineProps({
    topics: Array,
    editor: Number,
    agendaId: Number,
});

const textClasses = '';
const layoutClasses = 'p-6 mb-2 rounded-xl text-left align-center transition duration-300 ease-in-out border-2 border-transparent';
const lightClasses = 'bg-black/5 text-gray-500';
const darkClasses = 'dark:bg-white/5 dark:text-gray-400 hover:bg-white/10 dark:hover:border-white/20';

const list = ref([...props.topics]);
const showModal = ref(false);
const selectedTopic = ref(null);

const form = useForm({
    topics: list.value,
});

const openModal = (topic) => {
    selectedTopic.value = topic;
    showModal.value = true;
};

const closeModal = () => {
    showModal.value = false;
    selectedTopic.value = null;
};

const handleUpdateSuccess = () => {
    Inertia.reload({
        only: ['topic'],
        onSuccess: ({ props }) => {
            console.log('Updated Topic:', props.topic);
            selectedTopic.value = props.topic;
            showModal.value = true;
        }
    });
};

function onUpdate(event: SortableEvent) {
    list.value.forEach((topic, index) => {
        topic.topic_order = index + 1;
    });
    console.log('Order Updated', list.value);

    form.topics = list.value;

    form.put(route('topics.updateOrder'), {
        preserveScroll: true,
        onSuccess: () => {
            console.log('Topics order updated successfully');
        },
    });
}
</script>

<template>
    <div class="mb-6">
        <ol class="list-[upper-roman] list-outside">
            <VueDraggable ref="el" v-model="list" @update="onUpdate" v-if="editor === 1">
                <div v-for="topic in list" :key="topic.id" @click="openModal(topic)">
                    <div :class="textClasses + ' ' + layoutClasses + ' ' + lightClasses + ' ' + darkClasses">
                        <li class="ml-6 indent-2">
                            <div>
                                <h4>{{ topic.topic }}</h4>
                                <article class="ml-6">{{ topic.description }}</article>
                            </div>
                        </li>
                    </div>
                </div>
            </VueDraggable>
            <template v-else>
                <div v-for="topic in list" :key="topic.id" @click="openModal(topic)">
                    <div :class="textClasses + ' ' + layoutClasses + ' ' + lightClasses + ' ' + darkClasses">
                        <li class="ml-6 indent-2">
                            <div>
                                <h4>{{ topic.topic }}</h4>
                                <article class="ml-6">{{ topic.description }}</article>
                            </div>
                        </li>
                    </div>
                </div>
            </template>
        </ol>
    </div>
    <transition name="fade">
        <div v-if="showModal" class="modal-open grid h-screen place-items-center">
            <div class="modal-box absolute top-8 dark:bg-gray-800 bg-white max-w-screen-2xl">
                <TopicsShow :topic="selectedTopic" @close="closeModal" @update-success="handleUpdateSuccess" />
                <button @click="closeModal" class="mt-4 px-4 py-2 bg-blue-500 text-white rounded">Close</button>
            </div>
        </div>
    </transition>
</template>

<style scoped>
/* Add your styles here */
</style>

Show.vue:

<script setup>
import AgendaTopic from "@/Components/AgendaTopic.vue";
import {defineProps, defineEmits, ref} from 'vue';

const props = defineProps({
    topic: Object,
    written: String,
});

const emit = defineEmits(['close', 'update-success']);
const topic = ref(props.topic);

const handleUpdateSuccess = () => {
    emit('update-success');
};
</script>

<template>
    <button @click="$emit('close')">Close</button>
    <AgendaTopic :topic="topic" :written="written" @update-success="handleUpdateSuccess"/>
</template>

<style scoped>
/* Add your styles here */
</style>

AgendaTopic.vue:

<script setup>
import InputLabel from "@/Components/InputLabel.vue";
import TextInput from "@/Components/TextInput.vue";
import LinkRepeater from "@/Components/LinkRepeater.vue";
import TextArea from "@/Components/TextArea.vue";
import PrimaryButton from "@/Components/PrimaryButton.vue";
import moment from "moment";
import {router, useForm} from "@inertiajs/vue3";
import AttachmentRepeater from "@/Components/AttachmentRepeater.vue";
import ResourceButton from "@/Components/ResourceButton.vue";
import { EyeIcon, EyeSlashIcon, PencilSquareIcon, DocumentCheckIcon } from '@heroicons/vue/16/solid'
import { useConfirmDialog } from "@vueuse/core"
import DangerButton from "@/Components/DangerButton.vue";
import Checkbox from "@/Components/Checkbox.vue";
import SecureCheckbox from "@/Components/SecureCheckbox.vue";
import SubTopicRepeater from "@/Components/SubTopicRepeater.vue";
import { ref, watch } from "vue";
import { Inertia } from "@inertiajs/inertia";


const {
    isRevealed, reveal, confirm, cancel, onReveal, onConfirm, onCancel
} = useConfirmDialog();

const props = defineProps({
    topic: Object,
    written: String,
    icon: Function,
});

const topic = ref(props.topic);

watch(() => props.topic, (newTopic) => {
    topic.value = newTopic;
}, { immediate: true });

const emit = defineEmits(['update-success']);

const textClasses = '';
const layoutClasses = 'p-6 mb-6 rounded-xl flex-col text-left align-center w-full';
const lightClasses = 'bg-black/5 text-gray-500';
const darkClasses = 'dark:bg-white/5 dark:text-gray-400';

const topicForm = useForm({
    topic_id: props.topic.id,
    topic: props.topic.topic,
    description: props.topic.description,
    private_description: props.topic.private_description,
    subTopics: props.topic.subtopic.map(subtopic => ({
        subtopic: subtopic.subtopic,
        is_private: subtopic.is_private,
        subSubTopics: subtopic.subSubTopics.map(subSubTopic => ({
            subSubTopic: subSubTopic.subSubTopic,
            is_private: subSubTopic.is_private,
        })) || [],
        editable: false
    })) || [],
    links: props.topic.link.filter(link => link.is_private === 0).map(link => ({
        link: link.link,
        editable: false
    })) || [],
    privlinks: props.topic.link.filter(link => link.is_private === 1).map(link => ({
        link: link.link,
        editable: false
    })) || [],
    attachments: props.topic.attachment.filter(attachment => attachment.is_private === 0).map(attachment => ({
        attachment: attachment.attachment,
        editable: false
    })) || [],
    privAttachments: props.topic.attachment.filter(attachment => attachment.is_private === 1).map(attachment => ({
        attachment: attachment.attachment,
        editable: false
    })) || [],
    is_private: props.topic.is_private,
});

const cancelSubTopic = (index) => {
    topicForm.subTopics.splice(index, 1);
};

const addSubTopic = () => {
    topicForm.subTopics.push({ subtopic: '', subSubTopics: [], editable: true });
};

const removeSubTopic = (index) => {
    reveal();
    onConfirm(() => {
        topicForm.subTopics.splice(index, 1);
    });
};

const addSubSubTopic = (subIndex) => {
    topicForm.subTopics[subIndex].subSubTopics.push({ subSubTopic: '', editable: true });
};

const removeSubSubTopic = (subIndex, subSubIndex) => {
    topicForm.subTopics[subIndex].subSubTopics.splice(subSubIndex, 1);
};

const cancelSubSubTopic = (subIndex, subSubIndex) => {
    topicForm.subTopics[subIndex].subSubTopics.splice(subSubIndex, 1);
};

const cancelLink = (index) => {
    topicForm.links.splice(index, 1);
};
const addLink = () => {
    topicForm.links.push({ link: '', editable: true });
};

const removeLink = (index) => {
    reveal();
    onConfirm(() => {
        topicForm.links.splice(index, 1);
    });
};
const addPrivLink = () => {
    topicForm.privlinks.push({ link: '', editable: true });
};

const removePrivLink = (index) => {
    reveal();
    onConfirm(() => {
        topicForm.privlinks.splice(index, 1);
    });
};
const cancelPrivLink = (index) => {
    topicForm.privlinks.splice(index, 1);
};

const addAttachment = () => {
    topicForm.attachments.push({ attachment: '', editable: true });
};
const removeAttachment = (index) => {
    topicForm.attachments.splice(index, 1);
};
const addPrivAttachment = () => {
    topicForm.privAttachments.push({ attachment: '', editable: true });
};
const removePrivAttachment = (index) => {
    topicForm.privAttachments.splice(index, 1);
};


const updateTopic = () => {
    console.log('Update Topic Called');
    const payload = {
        topic_id: topicForm.topic_id,
        topic: topicForm.topic,
        description: topicForm.description,
        private_description: topicForm.private_description,
        subTopics: topicForm.subTopics.map(subtopic => ({
            subtopic: subtopic.subtopic,
            is_private: subtopic.is_private,
            subSubTopics: subtopic.subSubTopics.map(subSubTopic => ({
                subSubTopic: subSubTopic.subSubTopic,
                is_private: subSubTopic.is_private,
            })),
        })),
        links: topicForm.links.map(linkObj => ({ link: linkObj.link, is_private: 0 })),
        privlinks: topicForm.privlinks.map(linkObj => ({ link: linkObj.link, is_private: 1 })),
        attachments: topicForm.attachments.map(attachmentObj => ({
            attachment: attachmentObj.attachment,
            is_private: 0
        })),
        privAttachments: topicForm.privAttachments.map(attachmentObj => ({
            attachment: attachmentObj.attachment,
            is_private: 1
        })),
        is_private: topicForm.is_private,
    };

    console.log('Payload:', payload);

    Inertia.put(route('topics.update', { topic: topicForm.topic_id }), payload, {
        preserveState: true,
        onStart: () => {
            console.log('Inertia request started');
        },
        onSuccess: ({ props }) => {
            console.log('Inertia request successful, props:', props);
            topic.value = props.topic;
            emit('update-success');
        },
        onError: (errors) => {
            console.error('Inertia request errors:', errors);
        },
    });
};

const deleteTopic = () => {
    reveal();
    onConfirm(() => {
        router.delete(route('topics.destroy', props.topic.id));
    });
};
</script>

<template>
    <div v-if="written === '1'" class="p-6 mb-6 rounded-xl dark:bg-green-300/5 dark:text-green-400">
        <span>Your topic was successfully added!</span>
    </div>
    <div v-if="written === '2'" class="p-6 mb-6 rounded-xl dark:bg-green-300/5 dark:text-green-400">
        <span>Your topic was updated successfully!</span>
    </div>
    <div :class="textClasses + ' ' + layoutClasses + ' ' + lightClasses + ' ' + darkClasses">
        <h2 class="text-2xl">{{ topic.topic }}</h2>
        <h3>Meeting: {{ topic.agenda.meeting.meeting }}</h3>
        <h3>Team: {{ topic.agenda.meeting.team.name }}</h3>
        <h4>Created on: {{ moment(topic.created_at).format("LLL") }}</h4>
        <form @submit.prevent="updateTopic" id="updateTopic">
            <div class="my-6 text-right">
                <SecureCheckbox id="sensitive" value="String" class="size-6" v-model="topicForm.is_private"
                                :true-value="1" :false-value="0"/>
                <label class="ml-2" for="sensitive"
                       v-text="topicForm.is_private ? 'This topic contains sensitive information.' : 'Mark topic as sensitive'"></label>
            </div>
            <div id="topic_form" class="mt-6">
                <InputLabel for="topic" class="mb-3">Topic</InputLabel>
                <TextInput id="topic" v-model="topicForm.topic" class="mb-6"/>
                <InputLabel for="description" class="mb-3">Topic Details</InputLabel>
                <TextArea id="description" v-model="topicForm.description" rows="24" class="mb-6"/>

                <div id="private_description" v-show="topicForm.is_private"
                     class="dark:bg-red-500/20 py-6 px-6 mx-[-1.5rem] mb-6">
                    <InputLabel for="private_description" class="mb-3">Private Details</InputLabel>
                    <TextArea id="private_description" v-model="topicForm.private_description" rows="24"/>
                </div>

                <ol class="mb-2 items-center list-[upper-alpha] ml-6 mr-6 w-full">
                    <li v-for="(subtopic, index) in topicForm.subTopics" :key="index">
                        <SubTopicRepeater :id="'subTopic' + index" v-model="subtopic.subtopic"
                                          :subSubTopics="subtopic.subSubTopics" :editable="subtopic.editable"
                                          @addSubSubTopic="addSubSubTopic(index)" @remove="removeSubTopic(index)"
                                          @cancel="cancelSubTopic(index)"/>
                    </li>
                </ol>
                <ResourceButton type="button" @click="addSubTopic" class="mt-2">Add a Sub-Topic</ResourceButton>

                <div id="public_links" class="mb-6">
                    <h5>Public Links</h5>

                    <template v-for="(link, index) in topicForm.links" :key="index">
                        <div class="mb-2 flex items-center">
                            <LinkRepeater :id="'pubLink' + index" v-model="link.link" :editable="link.editable"
                                          @remove="removeLink(index)" @cancelLink="cancelLink(index)"/>
                        </div>
                    </template>
                    <template v-if="topicForm.is_private">
                        <ResourceButton type="button" @click="addLink" class="mt-2" :icon="EyeIcon">Add a Public Link
                        </ResourceButton>
                    </template>
                    <template v-else>
                        <ResourceButton type="button" @click="addLink" class="mt-2">Add a Link</ResourceButton>
                    </template>
                </div>
                <div id="private_links" v-show="topicForm.is_private"
                     class="dark:bg-red-500/20 py-6 px-6 mx-[-1.5rem] mb-6">
                    <h5>Private Links</h5>
                    <template v-for="(link, index) in topicForm.privlinks" :key="index">
                        <div class="mb-2 flex items-center">
                            <LinkRepeater :id="'privLink' + index" v-model="link.link" :editable="link.editable"
                                          @remove="removePrivLink(index)" @cancelLink="cancelPrivLink(index)"/>
                        </div>
                    </template>
                    <ResourceButton type="button" @click="addPrivLink" class="mt-2" :icon="EyeSlashIcon">Add Private
                        Link
                    </ResourceButton>
                </div>
                <div id="public_docs" class="mb-6">
                    <h5>Public Documents</h5>
                    <template v-for="(doc, index) in topicForm.attachments" :key="index">
                        <div class="mb-2 flex items-center">
                            <AttachmentRepeater :id="'pubLink' + index" v-model="doc.attachment"
                                                :editable="doc.editable" @remove="removeAttachment(index)"/>
                        </div>
                    </template>
                    <template v-if="topicForm.is_private">
                        <ResourceButton type="button" @click="addAttachment" class="mt-2" :icon="EyeIcon">Add a Public
                            Document
                        </ResourceButton>
                    </template>
                    <template v-else>
                        <ResourceButton type="button" @click="addAttachment" class="mt-2">Add a Document
                        </ResourceButton>
                    </template>
                </div>
                <div id="private_docs" v-show="topicForm.is_private"
                     class="dark:bg-red-500/20 py-6 px-6 mx-[-1.5rem] mb-6">
                    <h5>Private Documents</h5>
                    <template v-for="(doc, index) in topicForm.privAttachments" :key="index">
                        <div class="mb-2 flex items-center">
                            <AttachmentRepeater :id="'pubLink' + index" v-model="doc.attachment"
                                                :editable="doc.editable" @remove="removePrivAttachment(index)"/>
                        </div>
                    </template>
                    <ResourceButton type="button" @click="addPrivAttachment" class="mt-2" :icon="EyeSlashIcon">Add a
                        Private Document
                    </ResourceButton>
                </div>
            </div>
            <!-- Prevent implicit submission of the form -->
            <button type="submit" disabled style="display: none" aria-hidden="true"></button>
        </form>
        <form @submit.prevent="deleteTopic" id="deleteTopic"></form>
        <div>
            <!-- Actually submit -->
            <PrimaryButton type="submit" form="updateTopic" class="mt-3">Save Changes to Topic</PrimaryButton>
            <DangerButton form="deleteTopic" type="submit" class="ml-6">Delete Topic</DangerButton>
        </div>
    </div>
    <div class="modal" :class="{'modal-open': isRevealed}">
        <div class="modal-box fixed top-8 dark:bg-gray-800 bg-white">
            <h1 class="text-2xl mb-6">Confirm Delete</h1>
            <p>Are you sure you wish to remove this item?</p>
            <div class="modal-action">
                <ResourceButton @click="cancel">No</ResourceButton>
                <DangerButton @click="confirm">Yes</DangerButton>
            </div>
        </div>
    </div>
</template>

<style scoped>
/* Add your styles here */
</style>

app.js:

import './bootstrap';
import '../css/app.css';
import.meta.glob([
    '../images/**'
]);

import { createApp, h } from 'vue';
import { createInertiaApp } from '@inertiajs/vue3';
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
import { ZiggyVue } from '../../vendor/tightenco/ziggy';
import VueTailwindDatepicker from 'vue-tailwind-datepicker';

const appName = import.meta.env.VITE_APP_NAME || 'Laravel';


createInertiaApp({
    title: (title) => `${title} - ${appName}`,
    resolve: (name) => resolvePageComponent(`./Pages/${name}.vue`, import.meta.glob('./Pages/**/*.vue')),
    setup({ el, App, props, plugin }) {
        return createApp({ render: () => h(App, props) })
            .use(plugin)
            .use(ZiggyVue)
            .component('VueTailwindDatepicker', VueTailwindDatepicker)
            .mount(el);
    },
    progress: {
        color: '#4B5563',
    },
});

0 likes
1 reply
LaryAI's avatar
Level 58

The error this.resolveComponent is not a function typically occurs when there is an issue with how Inertia.js is set up or how components are being resolved. Here are a few steps to troubleshoot and resolve this issue:

  1. Ensure Inertia.js and Vue 3 are properly installed and configured: Make sure you have the correct versions of Inertia.js and Vue 3 installed. You can check your package.json to verify this.

  2. Check the Inertia setup in app.js: Ensure that the resolve function in createInertiaApp is correctly set up to resolve your Vue components. It looks like you are using resolvePageComponent from laravel-vite-plugin/inertia-helpers. Ensure this is correctly imported and used.

  3. Update the resolve function: Sometimes, the issue can be resolved by explicitly importing the components. Here’s an updated version of your app.js:

    import './bootstrap';
    import '../css/app.css';
    import.meta.glob([
        '../images/**'
    ]);
    
    import { createApp, h } from 'vue';
    import { createInertiaApp } from '@inertiajs/vue3';
    import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
    import { ZiggyVue } from '../../vendor/tightenco/ziggy';
    import VueTailwindDatepicker from 'vue-tailwind-datepicker';
    
    const appName = import.meta.env.VITE_APP_NAME || 'Laravel';
    
    createInertiaApp({
        title: (title) => `${title} - ${appName}`,
        resolve: (name) => {
            const pages = import.meta.glob('./Pages/**/*.vue');
            return pages[`./Pages/${name}.vue`];
        },
        setup({ el, App, props, plugin }) {
            return createApp({ render: () => h(App, props) })
                .use(plugin)
                .use(ZiggyVue)
                .component('VueTailwindDatepicker', VueTailwindDatepicker)
                .mount(el);
        },
        progress: {
            color: '#4B5563',
        },
    });
    
  4. Ensure the Inertia plugin is correctly used in your Vue components: Make sure you are using the Inertia plugin correctly in your Vue components. For example, in AgendaTopics.vue, ensure you are importing and using Inertia and useForm correctly:

    <script lang="ts" setup>
    import { ref } from 'vue';
    import { Link, useForm } from "@inertiajs/vue3";
    import { VueDraggable } from 'vue-draggable-plus';
    import { SortableEvent } from "sortablejs";
    import TopicsShow from '@/Pages/Topics/Show.vue';
    import { Inertia } from "@inertiajs/inertia";
    
    const props = defineProps({
        topics: Array,
        editor: Number,
        agendaId: Number,
    });
    
    const textClasses = '';
    const layoutClasses = 'p-6 mb-2 rounded-xl text-left align-center transition duration-300 ease-in-out border-2 border-transparent';
    const lightClasses = 'bg-black/5 text-gray-500';
    const darkClasses = 'dark:bg-white/5 dark:text-gray-400 hover:bg-white/10 dark:hover:border-white/20';
    
    const list = ref([...props.topics]);
    const showModal = ref(false);
    const selectedTopic = ref(null);
    
    const form = useForm({
        topics: list.value,
    });
    
    const openModal = (topic) => {
        selectedTopic.value = topic;
        showModal.value = true;
    };
    
    const closeModal = () => {
        showModal.value = false;
        selectedTopic.value = null;
    };
    
    const handleUpdateSuccess = () => {
        Inertia.reload({
            only: ['topic'],
            onSuccess: ({ props }) => {
                console.log('Updated Topic:', props.topic);
                selectedTopic.value = props.topic;
                showModal.value = true;
            }
        });
    };
    
    function onUpdate(event: SortableEvent) {
        list.value.forEach((topic, index) => {
            topic.topic_order = index + 1;
        });
        console.log('Order Updated', list.value);
    
        form.topics = list.value;
    
        form.put(route('topics.updateOrder'), {
            preserveScroll: true,
            onSuccess: () => {
                console.log('Topics order updated successfully');
            },
        });
    }
    </script>
    
  5. Check for any typos or incorrect imports: Ensure there are no typos or incorrect imports in your components. For example, make sure @inertiajs/vue3 is correctly imported and used.

By following these steps, you should be able to resolve the this.resolveComponent is not a function error and ensure that your Inertia.js setup works correctly with Vue 3.

Please or to participate in this conversation.