I'm a beginner on Gatsby. I try to display a batch of images in grid from a specific folder. I find a script to do that, but that's catch all pics from my data folder and my purpose is target a specific one. I try a script but that's don't work with allImageSharp when I try to filter this one allImageSharp(filter: { sourceInstanceName: { eq: "media" } }) {.
When I try allImageSharp(filter: { id: { regex: "/media/" } }) { just display a blank window, but that's work fine like that allImageSharp {
javascript
import React from "react"
import { graphql } from "gatsby"
import Img from "gatsby-image"
import Layout from "../components/layout"
const img_grid_style = {
display: "grid",
gridTemplateColumns: `repeat(auto-fill, 200px)`,
}
export default ({ data }) => (
<div>
<Layout title="IMAGE GRID"></Layout>
<div style={img_grid_style}>
{data.allImageSharp.edges.map(edge => (
<Img fluid={edge.node.fluid} />
))}
</div>
</div>
)
export const query = graphql`
query {
allImageSharp(filter: { sourceInstanceName: { eq: "media" } }) {
edges {
node {
id
fluid(maxWidth: 200, maxHeight: 200) {
...GatsbyImageSharpFluid
}
}
}
}
}
`
config
module.exports = {
plugins: [
`gatsby-transformer-sharp`,
{
resolve: `gatsby-plugin-sharp`,
options: {
// Available options and their defaults:
base64Width: 20,
// forceBase64Format: ``, // valid formats: png,jpg,webp // don't work on OSX
useMozJpeg: process.env.GATSBY_JPEG_ENCODER === `MOZJPEG`,
stripMetadata: true,
defaultQuality: 50,
failOnError: true,
},
},
{
resolve: `gatsby-source-filesystem`,
options: {
name: `media`,
path: `./media/`,
},
},
],
}
Change your path in your gatsby-source-filesystem to:
{
resolve: `gatsby-source-filesystem`,
options: {
name: `media`,
path: `${__dirname}/media/`,
},
},
The snippet above will match. rootOfYourProject/media, change it to your media path keeping the ${__dirname}.
Now, your filesystem can be filtered by media (without slashes).
export const query = graphql`
{
allFile(filter: {sourceInstanceName: {eq: "media"}}) {
nodes {
childImageSharp {
fluid {
base64
tracedSVG
srcWebp
srcSetWebp
originalImg
originalName
}
}
}
}
}
`
Since you can't use fragments in the GraphQL playground (localhost:8000/___graphql) I've used the extended version, however, you should use the ...GatsbyImageSharpFluid once applied to your code.
allFile or allImageSharp should do the trick.
I've fixed your project in this PR. The query was retrieving properly the results, however, you were missing some nested objects to get the final fluid image:
export default ({ data }) => {
return <div>
<Layout title="IMAGE GRID FROM SPECIFIC FOLDER"></Layout>
<div style={img_grid_style}>
{data.allFile.edges.map(({ node }) => (
<Img fluid={node.childImageSharp.fluid} />
))}
</div>
</div>
}
Related
Long story short(maybe it is not so short after all): on the same page I want to import and load dynamic components based on the selected module.
I have an object defined in the assets which contains the informations about the components that should be loaded for each module, it looks like this:
export const modules = {
module1: {
calls: {...},
components: [
{
url: 'shared/PreviewItem',
properties: [
{
name: 'width',
value: 'leftComponentWidth'
}
]
},
{
url: 'shared/ResizingDivider',
properties: []
},
{
url: 'forms/FormItem',
properties: [
{
name: 'width',
value: 'rightComponentWidth'
},
{
name: 'item',
value: 'item'
}
]
}
]
},
module2: {...}
}
Then I have my index page:
<template>
<div class="item-content">
<component
:is="component"
v-for="(component, i) in dataComponents"
:key="i"
v-bind="component.propertiesToPass"
#emit-action="emitAction($event)"
/>
</div>
</template>
<script>
export default {
data() {
return {
item: null,
rightComponentWidth: 50,
leftComponentWidth: 50,
dataComponents: []
}
},
created() {
this.importComponents()
},
methods: {
importComponents() {
this.dataComponents = []
modules[this.$route.params.module].components.forEach(
(component) => {
import(`~/components/${component.url}`).then((res) => {
res.default.propertiesToPass = []
component.properties.forEach((prop) => {
res.default.propertiesToPass.push({
[prop.name]: this[prop.value]
})
})
this.dataComponents.push(res.default)
})
}
)
},
emitAction(event) {
this[event.function](event.props)
},
changeComponentsWidth(event) {
this.leftComponentWidth -= event
this.rightComponentWidth = 100 - this.leftComponentWidth
}
}
}
}
</script>
As it is probably easy to understand I have to components and one divider between them that can be dragged to the right or to the left for resize the width of the other two components.
The components are getting loaded and imported correctly, and the props are passed right, so the width of both of the components in the start are 50 50.
The issue is that by doing [prop.name]: this[prop.value] I am setting the props to the value of this[prop.value] variable, and not to the variable itself, so, when I try to resize the components by using the divider, the variables get updated but the props get not.
Then the props are not responsive or reactive, are fixed.
The only way to update the props of the components is to add the following lines to the changeComponentsWidth() method:
this.dataComponents[0].propertiesToPass[0].width = this.leftComponentWidth
this.dataComponents[2].propertiesToPass[0].width = this.rightComponentWidth
But this is not a very dynamic way.
So My question is:
Is it possible to bind the props to the variable itself instead of just passing its value?
Or are there other "dynamic" ways to keep my props "responsive and reactive"?
I am trying to develop a simple plugin for CKEditor 5. My ultimate goal is to have the plugin emit the following HTML:
<div class="icms_note">
<div class="span-6 module-note">
<p>User can edit here</p>
</div>
<div class="clear"></div>
</div>
I was having issues with the user editable area being in the wrong spot so I reduced the output to just this:
<div class="icms_note">
<p>User can edit here</p>
</div>
and the data model looks like this:
<icms_note>
<paragraph>User can edit here</paragraph>
</icms_note>
So I can get the data model created correctly and the HTML ends up in the editor the way I expect but I can't edit the text in the paragraph. I click on the text and the cursor just jumps out. I've tried looking at other examples and the tutorials but I can't seem to get it to work right. My plugin code is below. Any help would be appreciated.
note.js
import Plugin from "#ckeditor/ckeditor5-core/src/plugin";
import NoteEditing from "./noteediting";
import NoteUI from "./noteui";
export default class Note extends Plugin {
static get requires() {
return [ NoteEditing, NoteUI ];
}
static get pluginName() {
return "IcmsNote";
}
}
noteui.js
import Plugin from "#ckeditor/ckeditor5-core/src/plugin";
import { ButtonView } from "#ckeditor/ckeditor5-ui";
export default class NoteUI extends Plugin {
init() {
const editor = this.editor;
editor.ui.componentFactory.add( "icms_note", (locale) => {
const button = new ButtonView(locale);
button.set({
label: "Note",
withText: true,
tooltip: true
});
button.on( "execute", () => {
editor.execute("insertNote");
});
return button;
} );
}
}
noteediting.js
import Position from "#ckeditor/ckeditor5-engine/src/view/position";
import NoteCommand from "./notecommand";
export default class NoteEditing extends Plugin {
init() {
this._defineSchema();
this._defineConverters();
this.editor.commands.add("insertNote", new NoteCommand(this.editor));
}
_defineSchema() {
const schema = this.editor.model.schema;
schema.register( "icms_note", {
inheritAllFrom: "$text",
allowIn: [ "$root", "$container" ],
isInline: true
});
}
_defineConverters() {
const conversion = this.editor.conversion;
conversion.for( "downcast" ).elementToElement({
model: "icms_note",
view: ( modelElementValue, conversionApi ) => {
const { writer } = conversionApi;
const outerDivElement = writer.createEditableElement("div", {class: "icms_note"});
return outerDivElement;
}
});//<div><div class=\"span-6 module-note\"><p>Enter note</p></div><div class=\"clear\"></div></div>
conversion.for( "upcast" ).elementToElement({
view: {
name: "div",
attributes: {
classes: [ "icms_note" ]
}
},
model: {
key: "icms_note",
value: viewElement => {
const val = viewElement.getChildren()[0].getChildren()[0].data;
return val;
}
}
});
}
}
notecommand.js
import Command from "#ckeditor/ckeditor5-core/src/command";
export default class NoteCommand extends Command {
constructor(editor) {
super(editor);
}
execute() {
console.log("NoteCommand#execute");
const model = this.editor.model;
const selection = model.document.selection;
model.change( modelWriter => {
let position = selection.getFirstPosition();
const icmsNote = modelWriter.createElement("icms_note");
const paragraph = modelWriter.createElement("paragraph");
modelWriter.insert(paragraph, icmsNote);
modelWriter.insertText("User can edit here", paragraph);
let positionElementName = position.parent.name;
while (positionElementName != "$root" && positionElementName != "$container") {
position = model.createPositionAfter(position.parent);
positionElementName = position.parent.name;
}
model.insertContent(icmsNote, position, null, {
setSelection: "after"
});
});
}
}
I have a Single Page that renders two component BlogDetailComponent and SidebarComponent and I am passing data using props to the component. BlogDetailComponent renders the blog detail and SidebarComponent renders the Related Blog
In Single Page route I am passing the slug which is dynamic to get the blog details.
Below is my vue-router code
import Vue from "vue";
import VueRouter from "vue-router";
import Single from "../views/Single.vue";
Vue.use(VueRouter);
const routes = [
{
path: "/blog-details/:slug",
name: "blog_details",
component: Single
}
];
const router = new VueRouter({
mode: "history",
base: process.env.BASE_URL,
routes
});
Problem that I am facing -
Whenever I click on the related blog link, the slug in the route changes but does not renders the page with updated blog data for the new slug.
I have implemented using beforeRouteUpdate, but the issue is that I have to call the getBlogDetail() and getRelatedBlogs() in created() as well as beforeRouteUpdate hooks in same page i.e. Single Page which I feel that is not the proper way to code, so any suggestion on how can I acheive this without having to call api two times in Single Page.
Below is my code for the single page
Single Page Code
import axios from 'axios'
import BlogDetailComponent from '#/components/BlogDetailComponent.vue'
import SidebarComponent from '#/components/SidebarComponent.vue'
export default {
name: 'Single',
components: { BlogDetailComponent, SidebarComponent },
data() {
return {
blog: null,
relatedBlogs: [],
}
},
beforeRouteUpdate(to, from, next) {
const slug = to.params.slug
this.getBlogDetail(slug)
this.getRelatedBlogs(slug)
next()
},
created() {
const slug = this.$route.params.slug
this.getBlogDetail(slug)
this.getRelatedBlogs(slug)
},
methods: {
getBlogDetail(slug) {
axios
.get(`${process.env.VUE_APP_BASE_URL}/blog-detail/${slug}`)
.then(response => {
if (response.data) {
this.blog = response.data
}
})
.catch(error => console.log(error))
},
getRelatedBlogs() {
axios
.get(
`${process.env.VUE_APP_BASE_URL}/blog/related/${this.$route.params.slug}`
)
.then(response => {
if (response.data) {
this.relatedBlogs = response.data
}
})
.catch(error => console.log(error))
},
}
}
1. You could trigger the event with a watcher:
const RelatedItems = {
template: `
<div>
Related items:<br />
{{ $attrs }}
</div>
`,
}
const BlogItems = {
template: `
<div>
Blog items:<br />
{{ $attrs }}
</div>
`,
}
const ViewItems = {
components: {
RelatedItems,
BlogItems,
},
data() {
return {
related: {},
blog: {},
}
},
watch: {
'$route.params.slug': {
handler(val) {
if (val) {
this.fetchRelated(val)
this.fetchBlog(val)
}
},
immediate: true,
deep: true,
},
},
methods: {
async fetchRelated(slug) {
const response = await fetch(`https://jsonplaceholder.typicode.com/todos/${slug}`)
const json = await response.json()
this.related = json
},
async fetchBlog(slug) {
const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${slug}`)
const json = await response.json()
this.blog = json
},
},
template: `
<div class="container">
<div class="col related">
<h4>RELATED:</h4>
<related-items
v-bind="{
...this.related,
}"
/>
</div>
<div class="col blog">
<h4>BLOG:</h4>
<blog-items
v-bind="{
...this.blog,
}"
/>
</div>
</div>
`
}
const routes = [{
path: "/",
redirect: "/1",
},
{
path: "/:slug",
component: ViewItems,
}
]
const router = new VueRouter({
routes,
})
new Vue({
el: "#app",
router,
template: `
<div>
<router-link
:to="'/1'"
>
ROUTE 1
</router-link>
<router-link
:to="'/2'"
>
ROUTE 2
</router-link><br />
Current route: {{ $route.params }}
<hr />
<router-view />
</div>
`
})
html,
body {
padding: 0;
margin: 0;
}
.container {
margin: 0 -16px;
display: flex;
}
.col {
padding: 0 16px;
height: 100%;
display: flex;
flex-direction: column;
}
.col.related {
width: 120px;
background: rgba(0, 0, 0, 0.2)
}
.col.blog {
width: calc(100% - 120px);
}
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<div id="app"></div>
2. Using named routes
const RelatedItems = {
// The "slug" prop is the same as the param is called
// in the router! If you want to rename it, look at the
// other component for an example.
props: ["slug"],
data() {
return {
related: {},
}
},
watch: {
// watching the prop - not the $route!
slug: {
handler(val) {
this.fetchRelated(val)
},
immediate: true,
},
},
methods: {
async fetchRelated(slug) {
const response = await fetch(`https://jsonplaceholder.typicode.com/todos/${slug}`)
const json = await response.json()
this.related = json
},
},
template: `
<div>
Related items:<br />
{{ related }}
</div>
`,
}
const BlogItems = {
// this component awaits a prop called "endpoint" - the
// router provides that with a small twist of re-naming
props: ["endpoint"],
data() {
return {
blog: {},
}
},
watch: {
// watching the prop - not the $route!
endpoint: {
handler(val) {
this.fetchBlog(val)
},
immediate: true,
},
},
methods: {
async fetchBlog(slug) {
const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${slug}`)
const json = await response.json()
this.blog = json
},
},
template: `
<div>
Blog items:<br />
{{ blog }}
</div>
`,
}
const routes = [{
path: "/",
redirect: "/1",
},
{
path: "/:slug",
components: {
blogItems: BlogItems,
relatedItems: RelatedItems,
},
props: {
// renaming the param - just to have an example,
// where the param passed as prop is modified a bit
blogItems: (route) => ({
endpoint: route.params.slug
}),
relatedItems: true,
},
}
]
const router = new VueRouter({
routes,
})
new Vue({
el: "#app",
router,
template: `
<div>
<router-link
:to="'/1'"
>
ROUTE 1
</router-link>
<router-link
:to="'/2'"
>
ROUTE 2
</router-link><br />
Current route: {{ $route.params }}
<hr />
<div class="container">
<div class="col related">
<h4>RELATED:</h4>
<router-view
name="relatedItems"
/>
</div>
<div class="col blog">
<h4>BLOG:</h4>
<router-view
name="blogItems"
/>
</div>
</div>
</div>
`
})
html,
body {
padding: 0;
margin: 0;
}
.container {
margin: 0 -16px;
display: flex;
}
.col {
padding: 0 16px;
height: 100%;
display: flex;
flex-direction: column;
}
.col.related {
width: 120px;
background: rgba(0, 0, 0, 0.2)
}
.col.blog {
width: calc(100% - 120px);
}
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<div id="app"></div>
This one is maybe a bit better if you have further plans with these components:
visuals & data is decoupled more,
the related & blog components are more encapsulated (thus easier to change),
by using props -> true in the router, the components are easier to control from the outside, easier to reuse
3. CONCLUSION
There are different ways to provide the same user experience - choose one, that suits your future plans:
if this is a part of your app that won't change much and/or is not that central, then I think the first is easier to see through & maintain (remember what is happening)
if these parts of your app might change more frequently (but the layout doesn't) and/or you'd like to build on these parts as more complex components, then I suggest the second.
I'm sure, that there are other solutions to your problem, but the baseline could be this: the router is pretty versatile. Using it with SFCs it can do virtually anything. :)
I am trying to create a gatsby site where I have projects posted, each with tags. I followed a basic tutorial on creating a gatsby blog, and am now working through gatsby's tags as documented on their website. Along the way while changing variable names from path to slug to avoid reserved words, I seem to have messed up because I can no longer create blogpost pages, nor tag pages. Each post is enclosed in a folder named by the post title, holding an index.md file defining its content, date, tag etc.
The following error is output when I try to run gatsby develop,
ERROR #11323
Your site's "gatsby-node.js" must set the page path when creating a page.
The page object passed to createPage:
{
"slug": "/an-example-post",
"component": "C:\\Users\\Monolith\\Documents\\programming\\webdev\\Gatsby-Portfolio\\src\\templates\\post.js",
"context": {
"slug": "/an-example-post"
}
}
My gatsby-node.js
const path = require('path');
exports.createPages = ({actions, graphql}) => {
const {createPage} = actions;
const postTemplate = path.resolve('src/templates/post.js');
const tagTemplate = path.resolve("src/templates/tags.js")
//const postTemplate = require.resolve('src/templates/post.js');
//const tagTemplate = require.resolve("src/templates/tags.js")
return graphql(`{
allMarkdownRemark{
edges{
node {
html
id
frontmatter{
slug
title
}
}
}
}
tagsGroup: allMarkdownRemark(limit: 2000) {
group(field: frontmatter___tags) {
fieldValue
}
}
}`)
.then(res => {
if(res.errors){
return Promise.reject(res.errors);
}
res.data.allMarkdownRemark.edges.forEach( ({node}) => {
createPage({
slug: node.frontmatter.slug,
component: postTemplate,
context: {
slug:node.frontmatter.slug
},
})
})
// Extract tag data from query
const tags = res.data.tagsGroup.group
// Make tag pages
tags.forEach(tag => {
createPage({
// path: `/tags/${_.kebabCase(tag.fieldValue)}/`,
slug: `/tags/${(tag.fieldValue)}/`,
component: tagTemplate,
context: {
tag: tag.fieldValue,
},
})
})
})
}
As previously mentioned I was concerned the path variable being a reserved word could be an issue, but I got further using it than omitting it so for now it has stayed.
Example post.js
import React from 'react';
import { graphql } from 'gatsby';
import Layout from "../components/layout"
export default function Template({data}) {
const {markdownRemark: post} = data;
return(
<Layout>
<div>
<h1 className="postTitle">{post.frontmatter.title}</h1>
<div className="tagGroup">
{post.frontmatter.tags.map((tag, index) => (
<div key={index}>
<h2 className = "tagStyle">{tag}</h2>
</div>
))}
</div>
<p>{post.frontmatter.description}</p>
<p>{post.frontmatter.date}</p>
{/* <h2 className="tagStyle">{post.frontmatter.tags + " "}</h2> */}
<div dangerouslySetInnerHTML={{__html: post.html}} />
</div>
</Layout>
)
}
export const postQuery = graphql`
#query BlogPostByPath($slug: String!) {
query($slug: String!) {
markdownRemark(frontmatter: { slug: {eq: $slug} }) {
html
frontmatter {
slug
title
description
date
tags
}
}
}
`
The tags.js is very similar to gatsby's default with slight alterations for content. Here is the Graphql query I am using.
Tags.propTypes = {
pageContext: PropTypes.shape({
tag: PropTypes.string.isRequired,
}),
data: PropTypes.shape({
allMarkdownRemark: PropTypes.shape({
totalCount: PropTypes.number.isRequired,
edges: PropTypes.arrayOf(
PropTypes.shape({
node: PropTypes.shape({
frontmatter: PropTypes.shape({
title: PropTypes.string.isRequired,
// slug: PropTypes.string.isRequired,
}),
fields: PropTypes.shape({
slug: PropTypes.string.isRequired,
}),
}),
}).isRequired
),
}),
}),
}
export default Tags
export const pageQuery = graphql`
query($tag: String) {
allMarkdownRemark(
limit: 2000
sort: { fields: [frontmatter___date], order: DESC }
filter: { frontmatter: { tags: { in: [$tag] } } }
) {
totalCount
edges {
node {
fields {
slug
}
frontmatter {
title
}
}
}
}
}
`
If anyone has any information to help point me in the right direction I would greatly appreciate it. I have been running through gatsby's documentation for hours and have yet to make substantial progress.
You are passing a tag in your context but expecting a slug:
createPage({
slug: `/tags/${(tag.fieldValue)}/`,
component: tagTemplate,
context: {
tag: tag.fieldValue,
},
})
And:
query($slug: String!) {}
The context is a way to pass data to your component template to use it to filter your data. Ideally should be a unique parameter (like a slug, id, etc). In that way, in your gatsby-node.js you should fetch all your posts, pass a unique field by context, and use that variable in your template to get all your needed data of each post.
In addition, you must provide a path, not a slug to your function.
You should change your createPage function to:
createPage({
path: `/tags/${tag.fieldValue}/`,
component: tagTemplate,
context: {
slug: tag.fieldValue,
},
})
Keep in mind that fieldValue should be a slug (or kind of) despite having different naming.
I would recommmend reading Gatsby's tutorial: https://www.gatsbyjs.com/tutorial/part-seven/
Background: I've built a standard single file component that takes a name prop and looks in different places my app's directory structure and provides the first matched component with that name. It was created to allow for "child theming" in my Vue.js CMS, called Resto. It's a similar principle to how WordPress looks for template files, first by checking the Child theme location, then reverting to the parent them if not found, etc.
Usage : The component can be used like this:
<!-- Find the PageHeader component
in the current child theme, parent theme,
or base components folder --->
<theme-component name="PageHeader">
<h1>Maybe I'm a slot for the page title!</h1>
</theme-component>
My goal : I want to convert to a functional component so it doesn't affect my app's render performance or show up in the Vue devtools. It looks like this:
<template>
<component
:is="dynamicComponent"
v-if="dynamicComponent"
v-bind="{ ...$attrs, ...$props }"
v-on="$listeners"
#hook:mounted="$emit('mounted')"
>
<slot />
</component>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
name: 'ThemeComponent',
props: {
name: {
type: String,
required: true,
default: '',
},
},
data() {
return {
dynamicComponent: null,
resolvedPath: '',
}
},
computed: {
...mapGetters('site', ['getThemeName']),
customThemeLoader() {
if (!this.name.length) {
return null
}
// console.log(`Trying custom theme component for ${this.customThemePath}`)
return () => import(`#themes/${this.customThemePath}`)
},
defaultThemeLoader() {
if (!this.name.length) {
return null
}
// console.log(`Trying default component for ${this.name}`)
return () => import(`#restoBaseTheme/${this.componentPath}`)
},
baseComponentLoader() {
if (!this.name.length) {
return null
}
// console.log(`Trying base component for ${this.name}`)
return () => import(`#components/Base/${this.name}`)
},
componentPath() {
return `components/${this.name}`
}, // componentPath
customThemePath() {
return `${this.getThemeName}/${this.componentPath}`
}, // customThemePath()
},
mounted() {
this.customThemeLoader()
.then(() => {
// If found in the current custom Theme dir, load from there
this.dynamicComponent = () => this.customThemeLoader()
this.resolvedPath = `#themes/${this.customThemePath}`
})
.catch(() => {
this.defaultThemeLoader()
.then(() => {
// If found in the default Theme dir, load from there
this.dynamicComponent = () => this.defaultThemeLoader()
this.resolvedPath = `#restoBaseTheme/${this.defaultThemePath}`
})
.catch(() => {
this.baseComponentLoader()
.then(() => {
// Finally, if it can't be found, try the Base folder
this.dynamicComponent = () => this.baseComponentLoader()
this.resolvedPath = `#components/Base/${this.name}`
})
.catch(() => {
// If found in the /components dir, load from there
this.dynamicComponent = () => import(`#components/${this.name}`)
this.resolvedPath = `#components/${this.name}`
})
})
})
},
}
</script>
I've tried SO many different approaches but I'm fairly new to functional components and render functions (never got into React).
The roadblock : I can't seem to figure out how to run the chained functions that I call in my original mounted() function. I've tried running it from inside the render function with no success.
Big Question
How can I find and dynamically import the component I'm targeting before I pass that component to the createElement function (or within my single file <template functional><template/>)?
Thanks all you Vue-heads! ✌️
Update: I stumbled across this solution for using the h() render function and randomly loading a component, but I'm not sure how to make it work to accept the name prop...
Late to the party, but I was in a similar situation, where I had a component in charge of conditionally render one of 11 different child components:
<template>
<v-row>
<v-col>
<custom-title v-if="type === 'title'" :data="data" />
<custom-paragraph v-else-if="type === 'paragraph'" :data="data" />
<custom-text v-else-if="type === 'text'" :data="data" />
... 8 more times
</v-col>
</v-row>
</template>
<script>
export default {
name: 'ProjectDynamicFormFieldDetail',
components: {
CustomTitle: () => import('#/modules/path/to/CustomTitle'),
CustomParagraph: () => import('#/modules/path/to/CustomParagraph'),
CustomText: () => import('#/modules/path/to/CustomText'),
... 8 more times
},
props: {
type: {
type: String,
required: true,
},
data: {
type: Object,
default: null,
}
},
}
</script>
which of course is not ideal and pretty ugly.
The functional equivalent I came up with is the following
import Vue from 'vue'
export default {
functional: true,
props: { type: { type: String, required: true }, data: { type: Object, default: null } },
render(createElement, { props: { type, data } } ) {
// prop 'type' === ['Title', 'Paragraph', 'Text', etc]
const element = `Custom${type}`
// register the custom component globally
Vue.component(element, require(`#/modules/path/to/${element}`).default)
return createElement(element, { props: { data } })
}
}
Couple of things:
lazy imports don't seem to work inside Vue.component, hence require().default is the way to go
in this case the prop 'type' needs to be formatted, either in the parent component or right here