Recursively Nested Components with VueJS - javascript

I'm trying to set up a parent-child hierarchy here are the relevant component definitions - I've simplified them to just show the structure of the hierarchy.
RootComponent.vue
<template>
<ComponentA></ComponentA>
</template>
ComponentA.vue
<template>
<!--...-->
<ComponentB></ComponentB>
</template>
ComponentB.vue
<template>
<!--Some standard html tags-->
<ComponentA></ComponentA>
<!--...-->
<ComponentC></ComponentC>
</template>
ComponentC.vue
<template>
<!--Some standard HTML tags-->
<ComponentD></ComponentD>
</template>
The problem is that ComponentA within ComponentB is not rendered and the following error message is displayed in the console:
Unknown custom element: - did you register the component correctly? For recursive components, make sure to provide the "name" option.
The top-level instance of ComponentA and ComponentB render correctly it is only the nested ComponentA one level down that does not render.
All of the components are created using the Vue.extend() method and have a name property defined on them. The components are imported by a components: property
ComponentA imports ComponentB
ComponentB imports ComponentA and ComponentC
Edit
Component A Importing
import ComponentB from "./ComponentB.vue"
export default Vue.extend({
name: "ComponentA",
//...
components: {
ComponentB
}
})
Component B Importing
import ComponentA from "./ComponentA.vue"
import ComponentC from "./ComponentC.vue"
export default Vue.extend({
name: "ComponentB",
//...
components: {
ComponentA,
ComponentC
}
})
Expected HTML Structure Rendered
<ComponentA>
<ComponentB>
<ComponentA>
<ComponentB>
<ComponentC></ComponentC>
</ComponentB>
</ComponentA>
<ComponentC></ComponentC>
</ComponentB>
</ComponentA>
Actual HTML Structure Rendered
<ComponentA>
<ComponentB>
<!--ComponentA is missing here-->
<ComponentC></ComponentC>
</ComponentB>
</ComponentA>
I've spent hours searching online for solutions but can't find any that solve the issue. Any pointers are greatly appreciated.

Related

Modify child component data added via slots from parent component in vuejs

I'm a bit new to the component's world and trying to figure out a thing, how the parent child relationship works in components. I've seen some examples of some component libraries where they have some parent child components to be defined and those are used as the child components. For example, table and tr:
<my-table> <!-- Parent -->
<my-tr> </my-tr> <!-- Child -->
</my-table>
Now, I assume, that child works for parent via slots. So the parent should be defined something like this:
<template>
<div>
<slot></slot>
</div>
</template>
Now the parent element can have multiple <my-tr> as well. And slot should be rendering all of those. However, I am trying to a similar thing but a little more complex than that.
I am trying to create a slider with this approach. Where there is a my-slider component and my-slider-item components used to define inside my-slider component. And then I want to control the visibility of the child components defined in the parent component slot by modifying it's properties.
It should be looking like this:
<my-slider>
<my-slider-item>Item 1</my-slider-item>
<my-slider-item>Item 2</my-slider-item>
<my-slider-item>Item 3</my-slider-item>
</my-slider>
my-slider component
<template>
<div class="my-slider">
<slot></slot>
</div>
</template>
my-slider-item component
<template>
<div class="my-slider__item">
<slot></slot>
</div>
</template>
Now how can I know in the parent that how many <my-slider-item> are defined in the parent slot and based on that I want to control the visibility of the child 1 at a time as it is going to work as the slider.
I'm not sure but I missing some basic concept here which I was not getting after looking at tons of example since yesterday. If anyone can please help here? Thanks a lot!
The parent-child relationship is actually established by importing the child component into the parent component, and including the child in the parent's 'components' option.
I created an example scenario with simple Parent and Child component definitions in order to show a standard relationship implementation. Built with Vue 2 and the Vue CLI.
MySlider.vue (parent)
<template>
<div class="my-slider">
<h4>My Slider</h4>
<my-slider-item v-for="(item, index) in sliderItems" :key="index" :sliderItem="item" />
</div>
</template>
<script>
import MySliderItem from './MySliderItem.vue'
export default {
components: {
MySliderItem
},
data() {
return {
sliderItems: [
{
name: 'Slider Item 1'
},
{
name: 'Slider Item 2'
},
{
name: 'Slider Item 3'
}
]
}
}
}
</script>
MySliderItem.vue (child)
<template>
<div class="my-slider-item">
<h5>{{ sliderItem.name }}</h5>
</div>
</template>
<script>
export default {
props: {
sliderItem: {
type: Object,
required: true
}
}
}
</script>

Two different routes using the same child components are evaluating data differently

Using Vue3 and vue-router4, two different components share the same child components. The component templates are setup as follows:
<!-- Component A -->
<template>
<ComponentA>
<Child />
</ComponentA>
</template>
<!-- Component B -->
<template>
<ComponentB>
<Child />
</ComponentB>
</template>
<!-- Child -->
<template>
<FilterResults />
</template>
These are the configured routes:
const routes = [
{
path:'/componenta/:param',
component: ComponentA
},
{
path:'/componentb',
component: ComponentB
}
]
const router = createRouter({
history: createWebHistory(),
routes,
})
Some data is setup in the Child component:
export default {
name: 'Child'
...,
data() {
return {
filters: {
size: [
{
selected: this.$route.query.size === "1m"
}
]
}
}
}
}
The above aims to set selected to true or false depending on whether a match is found in the route. The results is then passed into FilterResults as a prop:
<!-- Component A -->
<template>
<ComponentA>
<Child />
</ComponentA>
</template>
<!-- Component B -->
<template>
<ComponentB>
<Child />
</ComponentB>
</template>
<!-- Child -->
<template>
<FilterResults :filters="filters" />
</template>
With the above, the value of selected in the filter data is evaluated and the intended result is that when the components load, the filters in the $route are set to true in the data.
The problem is, where the child components of ComponentA and ComponentB are identical:
ComponentA /componenta/xyz?size=1m does not work as intended, where matches found in the route are not set to true in the filters data.
ComponentB /componentb?size=1m does work as intended, where matches found in the route are set to true in the filters data.
I can reproduce the problem only if router-view isn't keyed, so I'm assuming that's what you have.
If there are two router-links to the same component but with different size query parameters (as shown below), and you're clicking one link and then the other, Vue reuses the existing component instance, so the component's data() is not invoked, and the query parameter is not re-evaluated.
<router-link to="/componenta/xyz?size=1m">A (size=1m)</router-link> |
<router-link to="/componenta/xyz?size=0">A (size=0)</router-link> |
To ensure a new component is created for each route change, specify a router-view key on $route.fullPath (which includes the query parameters):
<router-view :key="$route.fullPath"></router-view>
demo
In my opinion, the problem is here, your data is not recalculating on route change,
try to modify local data on route change. Try to add debugger before return statement in data, it will come only one even if change the route.

Pass in a stylesheet as a prop for a render in a functional component

I'm using Next.js, React, Styled JSX, and Postcss if that matters. I need to style an imported component for a specific page. Since the stylesheets are created for a specific component at the time of render, I figured I could just put the custom styles for the component in with the page specific resources and pass them in. But I'm getting the following error:
Expected a template literal or String literal as the child of the JSX Style tag (eg: <style jsx>{`some css`}</style>), but got MemberExpression
I have two functional renders in two separate directories and files, one a page and the other a component:
src/pages/mypage/
index.js
styles.css
myComponentStyles.css
src/components/MyComponent
index.js
styles.css
Keeping in mind that file/directory referencing is not mirrored from my environment because it's not the problem, here's my code:
src/pages/mypage/index.js
import MyComponent from '../../components/MyComponent'
import styles from './styles.css'
import MyComponentStyles from './myComponentSyles'
const MyPage = () => {
return (
<div className="my-page-container">
<style jsx>{styles}</style>
<MyComponent styles={MyComponentStyles} />
</div>
)
}
export default MyPage
src/components/MyComponent/index.js
import styles from './styles.css'
const myComponent = props => {
return (
<>
<style jsx>{styles}</style>
<style jsx>{props.styles}</style>
<div className="my-component-main-container">MyComponent</div>
</>
)
}
export default MyComponent
How would I allow MyComponent to receive a stylesheet generated by another component?
Although this is not a direct solution to the problem, Styled JSX has a :global() pseudo selector that accomplishes the end goal of styling a component that is outside the scope of the current component. A working example for the given code is:
src/pages/mypage/styles.css
.my-page-container :global(.my-component-main-container){
color: white;
}
Here is what the Next.js documentation says for the :global() pseudo selector:
One-off global selectors
Sometimes it's useful to skip selectors scoping. In order to get a
one-off global selector we support :global(), inspired by css-modules.
This is very useful in order to, for example, generate a global class
that you can pass to 3rd-party components. For example, to style
react-select which supports passing a custom class via
optionClassName:
import Select from 'react-select'
export default () => (
<div>
<Select optionClassName="react-select" />
<style jsx>{`
/* "div" will be prefixed, but ".react-select" won't */
div :global(.react-select) {
color: red
}
`}</style>
</div> )

Using a component containing other components within a router-view in Vue.js

I am trying to build a layout using single-file components in Vue.js, with dynamic population and URLs using Vue-router. (I'm using the webpack template via vue-cli as well.)
It works as expected for my app.vue file-- containing the nav, sidebar, page head, and <router-view>-- and the <router-view> content appeared as expected when the correct <router-link> is clicked... until I tried to add subcomponents to the add-load component being called to the <router-view>. Now, nothing appears at all, despite not throwing any errors.
Admittedly, I am not basing my structure on any examples, as I couldn't really find any doing it the way I was hoping to. I wanted to use nested components by calling them like custom elements-- I think this makes the code much easier to read and maintain. I'm not entirely sure how to structure it otherwise, to be honest. Using multiple <router-view>s as siblings to each other seems counterintuitive to me.
I've tried a variety of combinations of how and where to import and call the components, and nothing has worked. The only way I can get any content to load is if I only call a single component for path: '/add-load'. Is it just impossible to use multiple components outside of your base app.vue? I find that hard to believe. Here's what I started with.
From my index.js:
import AddLoad from '#/components/AddLoad'
import AddLoad from '#/components/ProgressSteps'
import Stops from '#/components/Stops'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
components: {
Sidebar,
TopNav,
MobNav,
PageHead
}
},
{
path: '/add-load',
components: {
AddLoad,
ProgressSteps}
}
]
})
From my App.vue file (the multiple component behavior that I'd like to mimic is shown here):
<template>
<div id="app">
<div class="wrapper">
<Sidebar/>
<div class="container-fluid">
<TopNav/>
<MobNav/>
<div class="container-fluid">
<PageHead/>
<router-view></router-view>
</div>
</div>
</div>
</div>
</template>
<script>
import Sidebar from '#/components/Sidebar'
import TopNav from '#/components/TopNav'
import MobNav from '#/components/MobNav'
import PageHead from '#/components/PageHead'
export default {
name: 'App',
components: {
Sidebar,
TopNav,
MobNav,
PageHead
}
}
</script>
From my AddLoad.vue file:
<template>
<div class="add-load">
<div class="content-container container-slim">
<progress-steps/>
<router-link to="#stops">Stops</router-link>
<router-view></router-view>
</div>
</div>
</template>
<script>
import ProgressSteps from '#/components/ProgressSteps'
export default {
name: 'AddLoad',
component: ProgressSteps
}
</script>
Here is a link to a codesandbox, so you can see the full functionality. https://codesandbox.io/s/7k520xk0yq

Vuejs template inheritance

How can I use template inheritance (Like what jade has, extends file.jade and then the blocks with the same name would be overwritten)?
I know that I can do everything with composition, but for components like footer and header which appear on every single page except one or two (e.g.login page) I must write them on every single component. In my app I have a two level navigation and it seems painful to repeat them on every one of those child components :(
I know that I can use jade and then inherit a jade file within my components, but it seems wrong because I would have some jade and some Vue files, is there any other way to do this?
// Component.vue
<template lang="jade">
extends ./StandardLayout
block content
router-view
</template>
// StandardLayout.Vue
<template lang="jade">
div
navbar
div.container
div.spacer
div.row
block content
<template>
What I've settled for, is a layouts folder filled with jade layouts and I use them to extend my components. I used vue-cli with webpack template.
In the most general case if you have to repeat the same HTML over and over, one option you could use is <partial>s.
<partial name="header"></partial>
<div>My content content</div>
<partial name="footer"></partial>
Where you declare partials as
Vue.partial('header', '<h3>This is the title: {{title}}</h3>')
Vue.partial('footer', '<footer>Mini footer</footer>')
However if you are building a Single Page Application the strategy you could follow is to simply have a header and a footer around your <router-view>, here is a jsfiddle that demonstrates how to do.
https://jsfiddle.net/gurghet/vdqutw2y/
<header><h1>
My title: {{title}}
</h1></header>
<p>
<a v-link="{ path: '/foo' }">Go to Foo</a>
<a v-link="{ path: '/bar' }">Go to Bar</a>
</p>
<router-view></router-view>
<footer>Such footer, many links, wow!</footer>
If you know Chinses, please look it
// Base Component
<template>
<div class="base-thing special-class">
<Button />
</div>
</template>
<script>
import Button from './ButtonClick'
export default {
components: { Button }
}
</script>
// Inheriting Component
<script>
import BaseComponent from './BaseComponent'
import Button from './OtherButton'
export default {
extends: BaseComponent
components: {
Button
}
}
</script>
The Button of Child Component will be replaced OtherButton. We can do something in the OtherButton

Categories

Resources