Currently building a web page in Vue, and have hit a bit of an issue parsing and then rendering the <slot>'s child components.
I need to be able to take the slot, parse the components into an array, and then render those components for the end-user.
What I've Tried
I've tried many variations of things, most starting with this: this.$slots.default
This is the last version I tried
let slotComponents = [];
this.$slots.default.forEach(vNode => {
slotComponents.push(vNode);
});
But I've also tried selecting the elements within the vNode and using things like $childeren to select the components. No luck so far.
Potential Issues
The cause could be any number of things, but here is what I thought was going on (in order)
I'm not getting the components into the array properly
I'm not rendering them properly or missed something about how they render
Vue isn't supposed to do this?
Edit - Context
Seems like it would be easier if I gave you the full context of my specific problem.
Goal
To create a dynamic tab component. Should look like this.
// Example of component use
<tab-container>
<tab>
<!-- Tab Content -->
</tab>
<tab>
<!-- Tab Content -->
</tab>
<tab>
<!-- Tab Content -->
</tab>
<trash>
<!-- This one won't show up -->
</trash>
</tab-container>
In order to parse through this content, I needed to get the slot data out.
// Inside the <tabs-container> component
computed: {
tabs: function() {
let tabs = []
this.$slots.default.forEach(vNode => {
tabs.push(vNode);
});
return tabs;
}
}
// Inside the <tabs-container> template
<div>
{{tabs[currentTab]}}
</div>
You shouldn't be using template and computed properties if you want to programmatically render out <tab> inside <tab-container>. {{}} in templates are designed to perform basic operations of JS. The most appropriate way will be to use render function.
Render functions - Vue docs
Here is a working example that takes in few tabs components and shows only active tab component: https://jsfiddle.net/ajitid/eywraw8t/403667/
Related
Using React & material-ui, I have a pretty big tab container and want to keep data fetches local to each Tab component. I want to be able to essentially greedy load some of the Tab components so as soon as the Tab container is mounted, the Tabs with a greedyLoad prop passed to them are mounted (although not the active tab/visible) and make the fetch for the data they need.
The reason is some of the tabs need a count from the data I fetch in the tab label.
I understand I can fetch the data from the parent component and pass the data as a prop downwards, but I really would like to keep the fetch’s local to each tab component. I’ve seen it done at a previous company I worked at and totally forgot how it worked. Something with CSS I think. Thanks in advance
If you hide the component with CSS, your component will mount on the DOM, but it will be invisible to the user. We just need to add some inline css and make use of the display: none property
function myComponent(show) {
// TODO: fetch the data
return (
<div style={{display: show ? "block" : "none"}}>
<h1 >This component may be invisible!</h1>
<p>{data}</p>
</div>
);
}
I am just learning vue 3 and have the following problem. I use tabs library that provide only tab headers without content panels. So, my solution is the following:
<myTabs/><!-- < -- headers -->
<div v-for="tab in tabs" ><!-- < -- content -->
<keep-alive>
<component v-if="tab.active" :is="tab.component"></component>
</keep-alive>
</div>
Tabs are created this way:
const tabs: Array<Tab> = reactive<Array<Tab>>([])
So, I have tabs and for content panel I iterate tabs and show only active tab content. The only problem of this solution is that when order of tabs is changed (after swapping), then the order of content != order of tabs.
For example, I have two tabs (0,1). After swapping the order of tabs is (1,0), but the order of content is still (0,1). So, when I activate tab1 I see content of tab0. Could anyone help me to fix it?
Your v-for items need a key attribute so that Vue knows how to track item rendering. Without the key, Vue defaults to the item index within the v-for, which in your case, causes undesirable VNode reuse.
To resolve the issue, apply a unique key (e.g., an id property) on each v-for item:
<div v-for="tab in tabs" :key="tab.id">
Recently, I've been learning Gatsby.js & GraphQL and have been working on a small project. My goal with this project is to have simply one page that displays all posts on the page, meaning no slugs, etc referencing a specific page, just having all the markdown files converted to the template I have, then all on one page. An example of what I mean is below:
Looking through the Gatsby documentation, I can see how to make a list of markdown "blogs" but they're just links that you essentially click to take you to the actual post page which isn't what I want
I tried doing something similar to that, but my problem is I can't really pass the HTML to my component because it's made up of many UI components (Material UI card components). For example, I had something like this in my index.js file that displays all my posts
const Posts = edges
.filter(edge => !!edge.node.frontmatter.question)
.map(edge => <Grid item xs={8}><PostCard key={edge.node.id} question={edge.node.frontmatter.postTitle} postContents={}/></Grid>)
Obviously the problem there is, I can't pass dangerouslySetInnerHTML into postContents. I could pass edge.node.html but then the HTML tags themselves would be showing in the content. I know there's a better way to do this, which utilizes the template file in my templates folder but I'm not exactly sure how to, I had no luck finding much regarding this on stackoverflow or the Gatsby documentation.
Would appreciate any insight on how to achieve this result
Thanks!
If you cannot pass dangerouslySetInnerHTML into postContents, then you can instead pass the edge or node itself in, and then update that component to set the inner html of a div instead.
So your PostCard would look something like:
export default function PostCard({ node }) {
return (
<div key={node.id}>
<h1>{node.frontmatter.postTitle}</h1>
<div dangerouslySetInnerHTML={{ __html: node.html }} />
</div>
);
}
And your index.js could be simplified to:
const Posts = edges
.filter(edge => !!edge.node.frontmatter.question)
.map(edge => <Grid item xs={8}><PostCard node={edge.node} /></Grid>)
I am stuck trying pass data from Child A ($emit) component to Parent and from Parent to Child B (props).
Using nuxt.js I have:
layouts/default.vue
This default template will load a lot of components.
Those components will be used or not based on variable from child, the variable will set the v-if directive.
The children are the pages like:
pages/blog/index.vue
pages/about/index.vue
...
The goal is the Child set on Parent what components would be used, the flag can change anytime, the user can choose what will be rendered on admin area.
I have tried use local computed methods on child component, and vuex, no luck with both.
The idea on layouts/default.vue.
<template>
<div>
<TopBar v-if=showTopBar></TopBar>
<Nav v-if=showNav></Nav>
etc...
<nuxt />
</div>
</template>
<script>
import TopBar from "../components/TopBar";
import Nav from "../components/Nav";
etc...
export default {
data() {
return {
showTopBar: false,
showNav: false
etc...
};
},
}
</script>
On child already have use the $emit but no luck.
Child on this situation are pages, and the layout of those pages will be defined by variable from a fetch on the API, user can change the layout anytime.
The goal is have someting like double way between Child Components, example:
Calling route /blog will call pages/blog/index.vue
This would send to layout/default.vue using $emit what components would be rendered (choosed from user in admin area and fetched from API) and the component ID. (example: {topBar: true, topBarID: 2})
On layouts/default.vue after get the $emit from pages/blog/index.vue I would have for example TopBar false, and then not render it, or have received true with an ID, this Id will be send to TopBar as prop for render the customized TopBar made by user on Admin area.
Would be possible someone show an example how to get the pass those data for this specific cenario please?
(Does not matter if using local variables from the Child component or vuex, just looking for an example how to get the contents of variable from Child instead an plain object or undefinied object).
PS.: If there an better approach to deal with dynamic layouts, I am accepting suggestions too.
PS2.: I know I would use specific template per page, like layout/blog and layout/contact, etc... but since the idea is make an CMS, this would not fit on this scenario, I mean, from the admin area user should be able to create pages enabling or disabling components through an page Wizard (the idea is getting something like Wix, every component customization from user will be stored in the database using an Id, and on layouts user choose the previous components mounting the page, in the end all call will be made using the ids of those), and not need to add specific layouts programing, because this the Idea of set all possible components and layouts in layout/default.vue sounds at this moment an better approach, but if is not, I would love see other ways to get same goal.
The correct way to do it would be:
<child-component-1 :showNav.sync="showNav">
And within the child component you would update that by doing:
this.$emit('update:showNav', value)
The parent would define this property:
data() {
return {
showNav: default_value
}
}
You would have to pass that variable to every child component. Every child component would have to define it as a property.
Perhaps a better way to do it would be to instead create a simple store within nuxt and use that to house the settings.
I intend to set up the following React Component
<tab-box>
<title>Example</title>
<butons>
<button id="actionID1"></button>
<button id="actionID2"></button>
<button id="actionID3"></button>
</butons>
<content>
<tabList>
<tab id="tab1" label="label1"></tab>
<tab id="tab2" label="label2"></tab>
</tabList>
</content>
</tab-box>
Where I would like to pull out each label attribute for tab to set up a nav bar on top of the actual content.
The question is, how do I pull out the attributes from nested children? Or how can I restructure the component so that I don't have this issue?
Thought1: use a global Store service, children like tab populate the Store and parents may retrieve them when being mounted
UPDATE, Thought2: make labels a prop on tab-box, but I still don't feel quite right..
There are ways to do it, but i doubt it is what you ultimately want. You want the parent to obtain information about the child? This doesn't make sense as you are trying to create "components" and not some code specific to one task.
Think of it this way. The tabs should be using tab component to display its own information. Under what circumstance would the tab know more than its parent? If so then is it even a component? Who is using who?
React is suited for one way data flows from parent to child, so it makes more sense to hold information in tabs component, and pass pieces of info to child tabs.
<tabs>
<button text={tab[0].text}/>
<tab>
write transcluded children here
</tab>
</tabs>
Even this begs the question - why are you using so many components to begin with? Why not use css classes to represent tabs container and individual tabs like a normal person?
If you insist on making a tabs component, at least keep it all in one component, something like
<tabs data={[{title:"tab-title", content: (<p>stuff</p>)}]} />
Also I absolutely do not agree with defining tabs in a model/store. common sense is UI and data should be separate.
As someone else mentioned you can use ref, but i'd reserve that for very limited purposes such as getting a field from a form.