Use case
Given the following vue component:
<template>
<div>
<slot>{{ title }}</slot>
<ul>
<li v-for="label in labels" :key="label">
<input
type="checkbox"
v-model="checked"
:label="label"
:id="label"
:value="label"
/>
<label :for="label">{{ label }}</label>
</li>
</ul>
</div>
</template>
<script>
import Component from "vue-class-component";
import Vue from "vue";
#Component({
props: {
labels: {
type: Array,
},
title: {
type: String,
},
},
watch: {
checked: [
{
handler: "updateParent",
},
],
},
})
export default class CheckboxList extends Vue {
checked = [];
updateParent() {
this.$emit("update", this.checked);
}
}
</script>
The component will render a checkbox list of the labels prop it receives from the parent.
When two components are used within the same page, using a v-if toggle as follows:
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png" width="10%" />
<hr />
<button type="button" #click="state = 'numbers'">
Show number checklist
</button>
<button type="button" #click="state = 'letters'">
Show letter checklist
</button>
<CheckboxList
v-if="state === 'numbers'"
title="numbers"
:labels="numbers"
#update="checkedNumbers = $event"
></CheckboxList>
<CheckboxList
v-if="state === 'letters'"
title="letters"
:labels="letters"
#update="checkedLetters = $event"
></CheckboxList>
<div>
<span>Checked letters:</span> {{ checkedLetters }}
<span>Checked numbers:</span> {{ checkedNumbers }}
</div>
</div>
</template>
<script>
import Vue from "vue";
import HelloWorld from "./components/HelloWorld";
import CheckboxList from "./CheckboxList.vue";
import Component from "vue-class-component";
#Component({
components: {
CheckboxList,
},
})
export default class App extends Vue {
numbers = [1, 2, 3, 4, 5];
letters = ["a", "b", "c", "d"];
state = "numbers";
checkedLetters = [];
checkedNumbers = [];
}
</script>
This will create the following UI:
Why when toggling between the two components (using v-if) and checking the boxes - the data will mix up resulting the following behavior?
A Full working example can be found here: https://codesandbox.io/s/epic-resonance-plwvi?file=/src/App.vue
As part of Vues internal optimizations, it will strive to reuse components within its directives (i.e v-if or v-for for instance) as the default behavior.
This is documented in their docs.
Since unfortunately it's indeed what Vue will default to, consider adding a key prop with unique values in order to 'force' Vue to treat these as separate components:
<CheckboxList
key="numbers" <!-- Add this to distinct between the two -->
v-if="state === 'numbers'"
title="numbers"
:labels="numbers"
#update="checkedNumbers = $event"
></CheckboxList>
<CheckboxList
key="letters" <!-- Add this to distinct between the two -->
v-if="state === 'letters'"
title="letters"
:labels="letters"
#update="checkedLetters = $event"
></CheckboxList>
Note that this isn't required when components are rendered in one go - without conditional switches or looping:
<!--
Since both are rendered at the same time,
they will be considered separate components
and won't require a 'key' prop to distinguish between the two
-->
<CheckboxList
title="numbers"
:labels="numbers"
#update="checkedNumbers = $event"
></CheckboxList>
<CheckboxList
title="letters"
:labels="letters"
#update="checkedLetters = $event"
></CheckboxList>
Related
I am creating a website. I am a beginner. I have an issue. I have an array of react components. I don’t know can I use React components as the array elements. They are images, imported from the folder of my project. Also, I have an array of names of news companies. The idea is to create blocks with the name and image above. I want to create blocks according to the my images array length. So if the length of this array is 4, the cards I have 4. The issue is I can't display images, I imported them to my project. Main code is in the main page component. Also, I have a component called Author Card. In it, I have a React component, that receives name and image as the props and put them in the card Html block.
Here is my main page component code:
import React from 'react';
import AuthorCard from "./MainPageComponents/AuthorCard";
import BBC_Logo from '../assets/images/BBC_Logo.png';
import FOX_Logo from '../assets/images/FOX_Logo.png';
import CNN_Logo from '../assets/images/CNN_logo.png';
import ForbesLogo from '../assets/images/forbes-logo.png';
function MainPage(props) {
const channels = [
{
name: 'BBC',
index: 1
},
{
name: 'FOX',
index: 2
},
{
name: 'CNN',
index: 3
},
{
name: 'FORBES',
index: 4
},
];
const logos = [
<BBC_Logo key={1} />,
<FOX_Logo key={2}/>,
<CNN_Logo key={3}/>,
<ForbesLogo key={4}/>
];
return (
<div className="main-page">
<div className="main-page_container">
<section className="main-page_channels">
{channels.map( (channel) => {
logos.map( (logo) => {
return <AuthorCard name={channel.name} img={logo} />
})
})}
</section>
</div>
</div>
);
}
export default MainPage;
Here is my Author Card component code:
import React from 'react';
function AuthorCard(props) {
return (
<div className="author-card">
<div className="author-img">
{props.img}
</div>
<div className="author-name">
{props.name}
</div>
</div>
);
}
export default AuthorCard;
Please, help!
I would handle this a bit differently. First thing the way you import your logos is not imported as a component. Rather you get the path/src of the image which you can then use in a component. Read more about that here: https://create-react-app.dev/docs/adding-images-fonts-and-files/
So the way I would do this is to put the logo img src into your channels array and then pass that img src to the AuthorCard component. Then in the AuthorCard component your use a component to render the image. Like this:
import React from "react";
import BBC_Logo from "../assets/images/BBC_Logo.png";
import FOX_Logo from "../assets/images/FOX_Logo.png";
import CNN_Logo from "../assets/images/CNN_logo.png";
import ForbesLogo from "../assets/images/forbes-logo.png";
export default function App() {
return (
<div className="App">
<MainPage />
</div>
);
}
const channels = [
{
name: "BBC",
index: 1,
img: BBC_Logo
},
{
name: "FOX",
index: 2,
img: FOX_Logo
},
{
name: "CNN",
index: 3,
img: CNN_Logo
},
{
name: "FORBES",
index: 4,
img: ForbesLogo
}
];
function MainPage(props) {
return (
<div className="main-page">
<div className="main-page_container">
<section className="main-page_channels">
{channels.map((channel) => {
return <AuthorCard name={channel.name} img={channel.img} />;
})}
</section>
</div>
</div>
);
}
function AuthorCard(props) {
return (
<div className="author-card">
<div className="author-img">
<img src={props.img} alt="author card" />
</div>
<div className="author-name">{props.name}</div>
</div>
);
}
Here, we are using the map function to iterate over the channels array and render an AuthorCard component for each channel. We pass the name property to the AuthorCard component, as well as the corresponding logo from the logos array.
Note that we are also passing a key prop to the AuthorCard component to help React identify each component uniquely. In this case, we're using the index property of each channel object.
not sure whether what I am trying to do is possible but I would like to click the generate button which then returns a random item from the array and once that array has been returned, I would like to connect it to a div in my parent component which then returns the specific child component only.
MY HTML:
<div class="components" *ngFor="let component of components" >
<div class="brother" *ngIf="">
<app-brother></app-brother>
</div>
<div class="sister">
<app-sister></app-sister>
</div>
<div class="baby">
<app-baby></app-baby>
</div>
</div>
MY TS
export class ParentComponent {
#Input() component: string | undefined;
components = [
'brother',
'sister',
'baby'
];
getRandom(){
let myChild = this.components[Math.floor(Math.random() * this.components.length)];
console.log(myChild);
}
}
You can insert a component dynamicall but here it would be better to use a NgSwitch statement.
<div class="components"[ngSwitch]="component" >
<div class="brother" *ngSwitchCase="brother">
<app-brother></app-brother>
</div>
<div class="sister" *ngSwitchCase="sister">
<app-sister></app-sister>
</div>
<div class="baby" *ngSwitchCase="baby">
<app-baby></app-baby>
</div>
</div>
TS:
export class ParentComponent {
public component: string;
components = [
'brother',
'sister',
'baby'
];
getRandom(){
this.component = this.components[Math.floor(Math.random() * this.components.length)];
}
}
//inputtwo.vue
<template>
<div><input type="checkbox" v-model="checked" />one</div>
</template>
<script>
export default {
name: "inputtwo",
components: {},
data() {
return {};
},
};
</script>
//maincontent.vue
<template>
<div>
<div class="container" id="app-container" v-if="!checked">
<p>Text is visible</p>
</div>
<common />
</div>
</template>
<script>
export default {
name: "maincontent",
components: {},
data() {
return {
checked: false,
};
},
methods: {
hidecont() {
this.checked = !this.checked;
},
},
};
</script>
//inputone.vue
<template>
<div><input type="checkbox" v-model="checked" />one</div>
</template>
<script>
export default {
name: "inputone",
components: {},
data() {
return {};
},
};
</script>
How to hide content of checkbox from different components in Vuejs
I have three components called inputone(contains checkbox with v-model),inputtwo (contains checkbox with v-model),maincontent.(having some content and logic), So when user click on checkboxes from either one checckbox(one,two). i schould hide the content.
Codesanfdbox link https://codesandbox.io/s/crimson-fog-wx9uo?file=/src/components/maincontent/maincontent.vue
reference code:- https://codepen.io/dhanunjayt/pen/mdBeVMK
You are actually not syncing the data between components. The main content checked never changes. You have to communicate data between parent and child components or this won't work. And try using reusable components like instead of creating inputone and inputtwo for same checkbox create a generic checkbox component and pass props to it. It is a good practice and keeps the codebase more manageable in the longer run.
App.vue
<template>
<div id="app">
<maincontent :showContent="showContent" />
<inputcheckbox text="one" v-model="checkedOne" />
<inputcheckbox text="two" v-model="checkedTwo" />
</div>
</template>
<script>
import maincontent from "./components/maincontent/maincontent.vue";
import inputcheckbox from "./components/a/inputcheckbox.vue";
export default {
name: "App",
components: {
maincontent,
inputcheckbox,
},
computed: {
showContent() {
return !(this.checkedOne || this.checkedTwo);
},
},
data() {
return {
checkedOne: false,
checkedTwo: false,
};
},
};
</script>
checkbox component:
<template>
<div>
<input
type="checkbox"
:checked="value"
#change="$emit('input', $event.target.checked)"
/>
{{ text }}
</div>
</template>
<script>
export default {
name: "inputcheckbox",
props: ["value", "text"],
};
</script>
Content:
<template>
<div class="container" id="app-container" v-if="showContent">
<p>Text is visible</p>
</div>
</template>
<script>
export default {
name: "maincontent",
props: ["showContent"]
}
</script>
https://codesandbox.io/embed/confident-buck-kith5?fontsize=14&hidenavigation=1&theme=dark
Hope this helps and you can learn about passing data between parent and child components in Vue documentation: https://v2.vuejs.org/v2/guide/components.html
Consider using Vuex to store and maintain the state of the checkbox. If you're not familiar with Vuex, it's a reactive datastore. The information in the datastore is accessible across your entire application.
I have created a .Vue file to feature information on a cafe (Cafe Details Page). However, I would like to take parts of this details page and make it its own component, in order to manage any template updates more efficiently.
Therefore, I have created a Component (CafeHeader.vue) inside a components folder. I am trying to pass down the data from my array (Which is being used on my Cafe Details page) to this component using Props. However, I can't seem to get it to work.
The template for my Cafe Details Page is as below:
<template>
<div>
<div v-for="cafe in activeCafe">
<CafeHeader v-bind:cafes="cafes" />
<div class="content">
<p>{{ cafe.cafeDescription }}</p>
</div>
</div>
</div>
</template>
<script>
import CafeHeader from "./../../components/CafeHeader";
import cafes from "./../../data/cafes"; // THIS IS THE ARRAY
export default {
data() {
return {
cafes: cafes
};
},
components: {
CafeHeader,
},
computed: {
activeCafe: function() {
var activeCards = [];
var cafeTitle = 'Apollo Cafe';
this.cafes.forEach(function(cafe) {
if(cafe.cafeName == cafeTitle){
activeCards.push(cafe);
}
});
return activeCards;
}
}
};
</script>
Then, in a components folder I have a component called CafeHeader where I am wanting to use the data from the array which is previously imported to the Cafe Details page;
<template>
<div>
<div v-for="cafe in cafes">
<h1>Visit: {{cafe.cafeName}} </h1>
</div>
</div>
</template>
<script>
export default {
name: "test",
props: {
cafes: {
type: Array,
required: true
}
},
data() {
return {
isActive: false,
active: false
};
},
methods: {}
};
</script>
If in the CafeHeader component I have cafe in cafes, it does render data from the cafes.js However, it is every cafe in the list and I want just a single cafe.
<template>
<div>
<div v-for="cafe in cafes">
<h1>Visit: {{cafe.cafeName}} </h1>
</div>
</div>
</template>
The component also needed activeCafes on the v-for
<template>
<div>
<div v-for="cafe in activeCafes">
<h1>Visit: {{cafe.cafeName}} </h1>
</div>
</div>
</template>
I am a newbie at Vue, and I try to import and use the VueStrap input component as in the first example here.
This is how my component looks like (build-ui-x is under node_modules):
<template>
<div class="container">
<div class="card">
<div class="items-wrapper">
<bs-input v-model="input"
label="Username"
help="Only allows lowercase letters and numbers."
error="Insert username"
placeholder="Username can't start with a number."
pattern="^[a-z][a-z0-9]+$"
:mask="mask"
minlength="5"
readonly
required
icon
></bs-input>
</div>
</div>
</div>
</template>
<script>
import * as BuiInput from 'build-ui-x/src/Input.vue';
export default {
name: 'buildPrimaryCard',
data: function() {
return {
rdHeaderNames: ['SIN', 'Item', 'Rev'],
input: ""
}
},
computed: {
},
methods: {
mask: function (value) {
// change to lowercase, remove up to the first letter,
// and then remove all other unsuported characters
return value.toLowerCase().replace(/^[^a-z]+/,'').replace(/[^a-z0-9]/g,'');
}
},
mounted () {
},
components: {
BuiInput
}
}
</script>
build-ui-x is just a wrapper for vueStrap. Github link to the input component here.
The error I see on the console:
vue.esm.js?65d7:571 [Vue warn]: Unknown custom element: <bs-input> - did you register the component correctly? For recursive components, make sure to provide the "name" option.
found in
---> <BuildPrimaryCard> at D:\workspace\myReb\web\web-reb\src\components\build\BuildPrimaryCard.vue
<Home> at D:\workspace\myReb\web\web-reb\src\components\build\Build.vue
<App> at D:\workspace\myReb\web\web-reb\src\App.vue
<Root>
And Build.vue:
<template>
<div class="container">
<div class="button-three-group">
<button class="btn btn-primary">Save as draft</button>
<button class="btn btn-primary">Output to PDF</button>
<button class="btn btn-primary">Issued</button>
</div>
<build-primary-card></build-primary-card>
<build-model-card></build-model-card>
<build-revision-history-card></build-revision-history-card>
</div>
</template>
<script>
import * as PrimaryCard from './BuildPrimaryCard.vue'
import * as ModelCard from './BuildModelCard.vue'
import * as RevisionHistoryCard from './BuildRevisionHistoryCard.vue'
export default {
name: 'build',
computed: {},
methods: {},
mounted () {},
components: {
'build-primary-card' : PrimaryCard,
'build-model-card' : ModelCard,
'build-revision-history-card' : RevisionHistoryCard
}
}
</script>
I tried to find the keyword "bs-input" in the Input.vue file, but there was nothing like it. There should be a reference like that, if I use it on my template right?
I haven't used VueStrap before but looking at the code you should import the input component like:
import bsInput from 'vue-strap/src/Input'
Add it to components
components: {
BuiInput,
bsInput
}