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

madprabh's avatar

Dynamic Vue Component from server to client

Hey Folks,

So I have a situation where I am trying to send the name of the Vue component from the database and trying to render it on the UI. But for some reason it just doesn't work.

Here is my code. Ffrom server I am sending this data

"question_type": {
    "id": 2,
    "description": "Used when you want to collect numbers such as age, salary etc",
    "title": "Numeric",
    "html_code": "NumericBox",
    "icon": "",
    "created_at": "2024-03-29T05:47:09.000000Z",
    "updated_at": "2024-03-29T05:47:09.000000Z"
  }

On my frontend I have this

<component :is="page.question_ids[0].question.question_type.html_code" ></component>

But this doesn't work. Any clues where I am going wrong and is this even a good practice to do this?

0 likes
8 replies
LaryAI's avatar
Level 58

It looks like you're on the right track with using the <component :is="..."> syntax for dynamically rendering components based on the component name received from the server. However, there are a few things you should check and consider to ensure everything works correctly:

  1. Component Registration: Make sure that the component you are trying to render dynamically (in this case, NumericBox) is properly registered in your Vue application. If it's a global component, it should be registered like this:
Vue.component('NumericBox', {
  // component options
});

If it's a local component, make sure it's included in the components object of the parent component where you're using the <component> tag.

  1. Correct Data Path: Ensure that the data path you are using in the :is directive actually points to the correct data. Based on your example, double-check that page.question_ids[0].question.question_type.html_code correctly navigates through your data structure to retrieve the html_code value. You might want to console.log this value to verify it's what you expect.

  2. Reactivity: If the component name (html_code) is supposed to change dynamically in response to user actions or other asynchronous operations, make sure that the data changes are reactive and that Vue is aware of these changes. If you're setting this data in response to an API call or similar, you might need to ensure Vue reactivity by using Vue.set or updating a reactive property correctly.

Here's a basic example to illustrate how you might set this up:

// Assuming NumericBox is registered globally
Vue.component('NumericBox', {
  template: '<div>Numeric input component</div>'
});

new Vue({
  el: '#app',
  data: {
    currentComponent: 'NumericBox'
  }
});
<div id="app">
  <component :is="currentComponent"></component>
</div>
  1. Error Handling: Check for any errors in the console that might indicate what's going wrong. It could be a typo, a wrong path, or an issue with component registration.

  2. Best Practices: Regarding whether it's good practice to render components dynamically based on names from the server, it can be perfectly acceptable for many use cases, especially in highly dynamic applications. However, be cautious about the security implications, ensuring that component names are validated and that only allowed components can be rendered this way to avoid any potential for executing arbitrary or harmful code.

If after checking these aspects you're still facing issues, you might want to provide more details about the error messages or behaviors you're observing.

Amaury's avatar

@madprabh Hi. Don’t you forget to import the dynamic component… and all the dynamic one…

<script setup>
	import NumericBox from '@/Components/NumericBox.vue';
	import Other from  '@/Components/Other.vue';
	....
  </script>
madprabh's avatar

@amaury I did that but it still doesn;t work Full code

<template>
    <div class="bg-slate-100 h-full py-12">
        <div class="max-w-5xl  mx-auto sm:px-6 lg:px-8"> 
        	<div class="bg-white border-2 sm:rounded-lg ">	
	            <Title />
	            <div class="max-w-4xl mt-4 ml-4 mr-4 py-4">
	            	<!--v-for pages -->
	            	<div v-for="page in $page.props.event[0].pages">
	            		<!--v-for questions -->
	            		<!--page.question_ids[0].question.question_type.html_code-->
	            		{{page.question_ids[0].question.question_type.html_code}}
	            		<component :is="page.question_ids[0].question.question_type.html_code" ></component>
	            		
	            		<div v-for="question in page.question_ids">
	            			<!-- {{question.question}} -->
			            	<!-- <DropdownSingle />
			            	<NumericBox />
			            	<RadioHorizontal />
			            	<RadioVertical /> -->
	            		</div>
	            		
	            	</div>
	            	
	        	</div>
        	</div>
        </div>
    </div>
   <!--  </AppLayout> -->
</template>

<script setup>
	import Welcome from '@/Components/Welcome.vue';
	import Form from '@/Pages/Validate/Form.vue';
	import Title from '@/Pages/Survey/Title.vue';
	import CommentField from '@/Pages/Survey/Questions/CommentField.vue';
	import DropdownSingle from '@/Pages/Survey/Questions/DropdownSingle.vue';
	import NumericBox from '@/Pages/Survey/Questions/NumericBox.vue';
	import RadioHorizontal from '@/Pages/Survey/Questions/RadioHorizontal.vue';
	import RadioVertical from '@/Pages/Survey/Questions/RadioVertical.vue';
	
</script>
Amaury's avatar

@madprabh Maybe you can just try v-if since there is few components?

<Welcome v-if="page.question_ids[0].question.question_type.html_code === 'welcome'" />
<Form v-if="page.question_ids[0].question.question_type.html_code === 'form'" />
...

And I will create a function to simplify the if statment…

gych's avatar

Try like this, if it works you can add all the other cases.

<component :is="getComponent(page.question_ids[0].question.question_type.html_code)" >
<script setup>
	import Welcome from '@/Components/Welcome.vue';
	import Form from '@/Pages/Validate/Form.vue';
	import Title from '@/Pages/Survey/Title.vue';
	import CommentField from '@/Pages/Survey/Questions/CommentField.vue';
	import DropdownSingle from '@/Pages/Survey/Questions/DropdownSingle.vue';
	import NumericBox from '@/Pages/Survey/Questions/NumericBox.vue';
	import RadioHorizontal from '@/Pages/Survey/Questions/RadioHorizontal.vue';
	import RadioVertical from '@/Pages/Survey/Questions/RadioVertical.vue';
	
	const getComponent = (component) => {
		switch (component) {
			case "Welcome":
				return Welcome;
			case "Form":
				return Form;
		}
	}
</script>

You can also import the component inside the function without having to import them all at the top and without using the switch statement but that will require a different approach. You'll have to save the component path instead of the component name.

const getComponent = async (componentPath) => {
	componentPath = "@/Components/Welcome.vue"; // The path in this line is as example, it should come from your page data
	const { default: Component } = await import(componentPath);
	return Component;
}
madprabh's avatar

Thanks for your response @gych I did it in a similar manner to what you have shown but like this

<template>
    <div class="bg-slate-100 h-full py-12">
        <div class="max-w-5xl  mx-auto sm:px-6 lg:px-8"> 
        	<div class="bg-white border-2 sm:rounded-lg ">	
	            <Title />
	            <div class="max-w-4xl mt-4 ml-4 mr-4 py-4">
	            	<!--v-for pages -->
	            	<div v-for="page in $page.props.event[0].pages">
	            		
	            		<div v-for="qid in page.question_ids">
	            			
	            			<component v-if="qid.question" :is="componentMap[qid.question.question_type.html_code]" :qid="qid.question"></component>
	            			
			            	
	            		</div>
	            		
	            	</div>
	            	
	        	</div>
        	</div>
        </div>
    </div>
   <!--  </AppLayout> -->
</template>

<script setup>
	import Welcome from '@/Components/Welcome.vue';
	import Form from '@/Pages/Validate/Form.vue';
	import Title from '@/Pages/Survey/Title.vue';
	import CommentField from '@/Pages/Survey/Questions/CommentField.vue';
	import DropdownSingle from '@/Pages/Survey/Questions/DropdownSingle.vue';
	import NumericBox from '@/Pages/Survey/Questions/NumericBox.vue';
	import RadioHorizontal from '@/Pages/Survey/Questions/RadioHorizontal.vue';
	import RadioVertical from '@/Pages/Survey/Questions/RadioVertical.vue';
	
	const componentMap = {
	    Numericbox: NumericBox,
	    Commentfield: CommentField,
	    Radiohorizontal:RadioHorizontal,
	  }
</script>
gych's avatar

@madprabh No problem I'm glad it works now :) Please don't forget to mark your thread as solved.

MaverickChan's avatar

easy! I do this all the time.

use defineAsyncComponent and markRaw

import { defineAsyncComponent, markRaw, computed } from 'vue'

//get name from database , say game , movie, music, etc
const currentCompnent = markRaw(defineAsyncComponent(() => import(`../views/components/${name}.vue`)))
<component :is="currentComponent" />

Please or to participate in this conversation.