So I want to have many components nested to each other and included dynamically.
Lets assume simple case:
-container
-row
-container
-row
-widget
etc.
So how can I include container that will load row which will load previous component container in an elegant way (recursive I guess)
I want this functionality for more components than just container and row
I had the same problem myself right now:
it's usually webpack which cause this problem so you have two options:
Register your component Globally
On you child component, register the parent like this:
components: {
ComponentLoader: () => import('./ComponentLoader.vue')
}
you can read more here:
https://v2.vuejs.org/v2/guide/components-edge-cases.html#Circular-References-Between-Components
So to achieve my goal I have to build ComponentLoader and child components loaded from it.
ComponentLoader.vue
<template
v-for="(block, index) in data.children">
<component
v-if="component"
:is="component"
:key="`container-${block.id}-${index}`"
:data="block"/>
</template>
</template>
Which for instance will load Article component from its children:
ArticleComponent.vue
<template>
<SimpleArticle
:data="data"/>
<ComponentLoader
v-if="data.children"
:data="data"/>
</template>
So ArticleComponent will call ComponentLoader again if it has more children to load. This way works for me and is recursively going through the data tree.
Related
I'm currently learning vue.js and i'm struggling with the communication between parent and child components.
I'm trying to build two components (in separate files), a "accordion-container" and "accordion". The idea ist to then use them something like that on pages:
<accordion-container>
<accordion :title="'Accordion n1'">Insert HTML code here</accordion>
<accordion :title="'2nd Accordion'">Insert HTML code here</accordion>
</accordion-container>
Code for the container:
<template #closeAccordions="closeOtherAccordions">
<div class="accordion-container"><slot></slot></div>
</template>
<script>
export default {
props:['title'],
methods:{
closeOtherAccordions: function(){
console.log('Emit from child component received')
},
},
data: function() {
return {
}
}
};
</script>
Code for the accordions:
<template>
<div class="accordion" v-bind:class="{ open: isOpen }" :data-title="title">
<div class="title" #click="toggleAccordion">
<p>{{title}}</p>
</div>
<div class="content"><slot></slot></div>
</div>
</template>
<script>
export default {
props:['title'],
methods:{
toggleAccordion: function(){
this.isOpen = !this.isOpen
this.$emit('closeAccordions')
}
},
data: function() {
return {
isOpen: false
}
},
};
</script>
On the accordion i'm trying to emit "closeAccordions" (with the method toggleAccordion())
Then on the parent (accordion-container) i'd like to "listen" for that emit (with :closeAccordions="closeOtherAccordions"), and then execute a method on the parent.
But that method does not get called when i click the accordions.
Is my idea even possible? (Open to other ideas :) )
It won't work that way. The parent component cannot directly communicate to any components rendered within its slots via events, props, or by any other means that can only be achieved at the site where the slot contents are directly rendered (the container component doesn't control this).
When you are designing a component and you put a <slot> in the template, all you are doing is designating an insertion point within the template that users of the component can inject their own content.
You have 4 options:
(Advanced) Write the render function by hand and override the rendered slot vnodes to inject your own event listeners, props, etc.
Expose an API using scoped slots where you pass some data or methods to the slot which the user of the component would have to hook up in order for the component to operate correctly. Users of the component would have to remember to hook everything up correctly between the container and each accordion, so it's not ideal in this situation, but in general it is useful when you want to leave some of the functionality up to the user as to how the parent and children should operate.
Don't use events to communicate between the container and accordions, instead the accordions can call methods on the container directly via this.$parent.
Use provide/inject to allow the container to provide an API that each accordion can inject and use.
(3) is the recommended approach in this situation. The container and accordion components should be tightly coupled here. The accordion component can (and should) only be used directly within the container component, so it's OK if they communicate directly like that.
// Change this
this.$emit('closeAccordions')
// To this
this.$parent.closeOtherAccordions()
For more complicated components, (4) might be better.
I am using this lazyload directive here:
https://itnext.io/lazy-loading-images-with-vue-js-directives-and-intersectionobserver-d0eb390cad9
<li v-for="entry in entries">
<router-link :to="'/d/' + entry.title">
<img :title="entry.title" :data-url="entry.imageUrl" v-lazyload />
</router-link>
</li>
Problem is that whenever the route changes to another url, the first set of images that are displayed on the screen are the same images that were on the previous page!
It seems that vuejs is reusing HTML elements from another page to the next, but the lazyload directive is not aware of this.
How can I prevent this to happen and force Vue to re-render elements ?
The problem is that the component is being reused as Vue thinks that only the data is being changed. To solve this issue you should tell Vue to re-render the component when the path changes. To do that you can give a unique key to <router-view />
something like this:
<router-view :key="$route.fullPath"/>
This can be useful when you want to:
Properly trigger lifecycle hooks of a component
Trigger transitions
I'm using Vue.js in a project and I encountered a problem on how I should structure my components.
Suppose I have a component1 (we'll call this one as ItemView) that shows the information on an item from a list of items, and component2 (as EditDialog) that shows a modal to edit the item's information. I have 3 approaches in mind but I'm not sure which is the best approach in terms of readability, maintainability, scalability (and the like).
1. Component2 within Component1
For every instance of ItemView, there will be an EditDialog component.
Parent component
<template>
<div>
<item-view
v-for="item in items"
:key="item.id"
:item="item"
/>
</div>
</template>
ItemView component
<template>
<div>
<!-- show item information here -->
<edit-dialog :item="item"/>
</div>
</template>
2. Component1 and Component2 under the same Parent
ItemView emits an event to edit the item.
<template>
<div>
<item-view
v-for="item in items"
:key="item.id"
:item="item"
#edit="editItem(item)"
/>
<edit-dialog ref="editDialog"/>
</div>
</template>
<script>
import ...
export default {
components: { ... },
data: () => { ... },
methods: {
editItem(item) {
this.$refs.editDialog.edit(item)
}
}
}
</script>
3. Using an EventBus
This approach is similar with approach #2, but instead of using the parent component for component communication, an EventBus is used.
I am not sure which among these is the best approach to follow or if any of these approaches are right or wrong and there are better methods other than the ones above. If I were to choose, I'm leaning towards the first approach.
What is the best approach for this kind of problem or is there something better?
Well, I would said the one who suits the most to you and seems more maintenable.
Anyway, we have to keep in mind here that the edit-dialog mode is a overlayed modal of your webpage which is listing item-views. At this point, first solution isn't the good one for me, because the components edit-dialog would be repeated for each ItemView components, which is pointless because (I guess) we can't open more than one modal at the same time.
The second one is more suitable I think, and since the parent component is a kind of ItemWrapper / Manager, I don't see any problem to handle some event logic in here. Using an EventBus for this kind of feature that concerns two nearby UI components seems to me a bit exagerated.
On the performance side, I will let someone more experimented than me talk about it. Hope it helps.
I'm using React Router and I'm trying to link to a sub-component in another route by id. Basically what would usually be done using the <a href="www.url.com/profile/#profile-header-id">.
I'm not sure if there's a built in way for react router to do this, but if not perhaps I can manually trigger the link at a later point when I know the element has been rendered.
The issue isn't linking to another route which of course is done with the Link from react router. The issue is linking to an element which is found in the rendered HTML of the linked component.
Less Abstract Code Example:
So let's say my router is
<Route path"/A" component={A}>
<Route path"/B" component={B}>
component A has the following render:
render(){
<div>
// A looooot of text and other HTML elements
<div id="relevant-to-B">
// relevant stuff for component B
</div>
</div>
}
Now in component B, I want a Link that not only takes me to the Route "/A", but also scrolls to the element of id #relevant-to-B, thereby skipping all the irrelevant stuff.
I've recently started learning React and I'm trying to build a simple audio player. I'm currently using this example as a reference but it's built in one file
https://github.com/CezarLuiz0/react-cl-audio-player
The one I'm trying to make is done in a "React" way where the UI has reusable components but I'm having trouble separating my code into meaningful and working components. For example, if I try to move some of the rendering code from the parent component (AudioPlayer) into (PlayButton), the audio methods that is created on the mounting of the parent component suddenly becomes inaccessible to the child components.
Here is my code repo.
https://github.com/vincentchin/reactmusicplayer
It works now but I'd like to improve it. Also it'd be great if someone can point out huge flaws in this since I'm sure I've broken some rules or standards to coding in React.
You can access parent component's methods from a child component by passing the method as a prop, and then invoking it inside the child component.
For example (in the child component's render method):
<button onClick={this.props.methodFromTheParent}>Click me</button>
You can also pass arguments to these methods:
<button onClick={this.props.methodFromTheParent.bind(null, 'Hello')}>Click me</button>
Remember to pass in null instead of this as the first argument when binding values to a method belonging to a parent component.
I skimmed through your repo as well. You could clean up the AudioPlayer component a lot by putting the different elements into their own components.
The render method could look something like this:
render() {
return (
<div>
<PlayButton onClick={this.togglePlay} playing={this.state.playing} />
{!this.state.hidePlayer ?
(<Player
playerState={this.state}
togglePlay={this.togglePlay}
setProgress={this.setProgress}
...
/>) : null}
</div>
);
}
And then inside the newly-created Player component:
render() {
var pState = this.props.playerState; // Just to make this more readable
return (
<div className="player">
<PlayButton onClick={this.props.togglePlay} playing={pState.playing} />
<Timeline
currentTimeDisplay={pState.currentTimeDisplay}
setProgress={this.props.setProgress}
progress={pState.progress}
...
/>
<VolumeContainer
onMouseLeave={this.props.noShow}
setVolume={this.setVolume}
toggleMute={this.toggleMute}
...
/>
</div>
);
}
You can break the layout into as many nested components as is needed and makes sense.
Remember to actually add the onClick attribute inside the child components as well (<button onClick={this.props.onClick}>Play</button>).