In Vue 2 I used to be able to access a property on component children (rendered inside a v-for loop using this.$refs and a dynamically-assigned :ref).
The same code in Vue 3 fails, and when I log out this.$refs the object is empty.
Here I'm wanting to access an 'isOrderable' property on all children. The problem appears to be with :ref="product.id" being a variable. If I change it to ref="foobar" then I do get the last child in this.$refs.foobar. But it vue2 me an array back containing all children components.
<script>
import productItem from "./Product.vue";
export default {
props: ["products"],
components: {
'product-item': productItem
}
methods: {
addAllProducts() {
const orderable = this.products.filter((p) => this.$refs[p.id][0].isOrderable);
...
}
}
}
</script>
<template>
<form>
<div v-for="product in products" :key="product.id">
<product-item :product="product" :ref="product.id" />
</div>
<button #click="addAllProducts">Add All</button>
</form>
</template>
Obviously something changed in vue3 but I can't find any info about it. There's plenty of info on this.$refs, and but it all has to do with accessing refs from the composition API.
Any help appreciated.
In vue 3 they change how refs work with arrays, now you need to pass a function and have a state on your data to keep track of your refs https://v3-migration.vuejs.org/breaking-changes/array-refs.html#frontmatter-title.
I don't know how your code is structured but maybe there is a better solution to your problem than using refs, if the logic that toggles if a product-item is orderable lives inside the product-item component you can have an event that emits when the orderable value is changed an update an array of orderableProducts with the id of each product, you can even use that in a v-model with the multiple v-models options of vue3. in that way you don't need to hold a reference of the dom just to filter by the ones that are orderable.
Related
Hello I have Dots and Carousel component:
<Dots :carousel="$refs.carousel" />
<div>
some data
</div>
<carousel
ref="carousel"
:per-page="4"
:autoplay="true"
:autoplay-timeout="3000"
:mouse-drag="false"
>
....
</carousel>
When I try pass to prop on Dots component, I get undefined in prop data, why? I need Dots component to be before the Carousel component. This is important. I think, that this is reason why does not it work. If I put Dots component after carousel component, then working good.
In Dots component I have:
export default {
name: 'Dots',
props: ['carousel']
}
In mounted when I try call console.log(this.carousel), I get undifined. But I need get a component data of Carousel. How I can do it?
Sandbox test: https://codesandbox.io/s/flamboyant-monad-5bhjz?file=/src/components/Test.vue
If you only need to have Dot before your Carousel for aesthetic reason you can reorder your template (eg: https://developer.mozilla.org/fr/docs/Web/CSS/order).
You will be able to put you Dot component after Carousel to get its ref, but it will be show before.
I'm having an issue with component binding and state.
This is my html template:
<div class="ts-panel content">
<!--ko with: states.createState-->
<div data-bind="component: 'customer-create'">Testing CreateState</div>
<!--/ko-->
<!--ko with: states.lookupState-->
<div data-bind="component: 'customer-search'">Testing LookupState</div>
<!--/ko-->
</div>
This is my javascript
var myDataModel = function () {
var self = this;
self.states = {};
self.states.createState = ko.observable(true);
self.states.lookupState = ko.observable(false);
self.states.currentState = ko.observable(self.states.createState);
self.states.changeState = function (state) {
var currentState = self.states.currentState();
currentState(false);
self.states.currentState(state);
state(true);
}
};
return myDataModel;
I'm using another script to control which state I'm in by binding click events to certain buttons.
The problem I'm running into is that when I change the current state, the component bindings reset the state of the component. Eg. on the customer-create component, I fill out a form, then change to the lookupState, then change back to the createState, the form values are gone.
I think this is happening because the components are getting wiped out and recreated every time.
I also think that one solution to this is to store everything at the root level (i.e. the component that stores the states) and pass that all down when required to the individual components. However, I'd really like to keep the component-specific information inside those components.
Is there a way to store the state of the components or maybe store the components in a variable and bind to it that way?
From the documentations:
If the expression you supply involves any observable values, the expression will be re-evaluated whenever any of those observables change. The descendant elements will be cleared out, and a new copy of the markup will be added to your document and bound in the context of the new value.
The behaviour is same for the if binding as well. You could use the visible binding for this. This just hides and shows the div without actually removing it from the DOM. There is no containerless control flow syntax for visible. So, you'd have to add it to the div
<div data-bind="component:'customer-create', visible: states.createState">Testing CreateState</div>
I wrote a react component in render props way,it will call children function with 3 react component object ( not sure the name exactly, the variable generated by executing jsx (<div>...</div>) );
<PaginatedTable> Usage example:
<PaginationTable data={data} ...otherprops>
{({ SearchBar, Table, PaginationBar })=>
(<div>
{SearchBar}
{Table}
{PaginationBar}
</div>)
}
</PaginationTable>
with render props, I'm so glad that I can custom these 3 child component object very easily such as rearrange order or adding custom elements between these three.
{({ SearchBar, Table, PaginationBar })=>
(<div>
{PaginationBar}
<h1> my custom search bar text </h1>
{SearchBar}
{Table}
</div>)
}
But now I wish more than arrange order inside , I wish I can move {SearchBar} out of to the same layer of 's sibling 's children such as this picture.
working demo: https://codesandbox.io/s/23q6vlywy
I thought this may be anti-pattern to the unidirectional data flow of React.
Extract {SearchBar} to another independent component then use it as <SearchBar ... /> inside of <ToolBarArea /> is what I learnd from React Doc.
But in this way, I have to do "lifting state up" and write similar function and states already had in <PaginationTable /> like below **text** parts are functions and states already had in <PaginationTable />
class ToolBarArea extends Component{
render(){
return(
<div>
// text
<SearchBar onChange={**this.props.onSearchBarChange**} />
//.... other text or elements
</div>);
}
}
class ContainerArea extends Component {
state={
**searchBarText:'',**
tableData : [{...}, {...}]
}
**onSearchBarTextChange = (event)=>{
this.setState({ searchBarText: event.target.value });
}
filterdTableData = ()=> this.state.tableData.filter(d=>d.name.includes(this.state.searchBarText);
**
}
I really hope there is a way I can simply move the variable {SearchBar} in the render props function out to without knowing is in the parent or parent's sibling or anywhere in the DOM tree.
such as
<ToolBarArea>
{SearchBar} // SearchBar from <PaginationTable />
</ToolBarArea>
Is there a way to reuseonSearchBarTextChange and filtedTableData functions and these **text** codes I already wrote in <PaginationTable /> ?
I believe you hit the nail on the head when you referred to lifting state. If you already wrote a similar function then your best option may be to 'abstract' that function so that it applies to both use cases. You could use a simple flag to differentiate the unique execution each needs. Then finally pass the function down to both components.
If you're adamant about avoiding this approach you could technically get around it by using event listeners to handle data transfer or watch variables in the window but this is for sure an anti-pattern.
On component1.vue I have
export default {
data () {
return {
editItemNumber: null,
editBreakdownNumber: null
}
}
On component2.vue I have a table populated by an array
On that table is an edit button. Among the item in that table is itemNumber value. Which I need to assign from that particular row clicked to the editItemNumber on component1.vue
<b-table show-empty bordered striped hover :items="itemTableList" :fields="fields">
<template slot="actions" scope="row">
<b-btn variant='success' size="sm" #click.stop="edit(row.item,row.index,$event.target)">Edit</b-btn>
</template>
</b-table>
As you see above originally all of this was on one component and I was just calling to an edit function which repopulated the v-models with that rows contents. But now with everything split among components I don't know how to edit this to do what I've been tasked with.
I've never used JavaScript, vue or much of anything beyond basic HTML. I'm a .NET dev who's been tasked with helping out on some web based work and I'm floundering. So any help is appreciated.
The preferred way to move data between components is with events.
Normally you use props to pass data from a parent component to a child, and events to pass from a child up to a parent.
So the editmethod of C2 can be something like
edit(item, index, target) {
const payload = {
item,
index,
target
};
this.$emit('edit', payload);
}
Then you just have to listen to that event in C1. Notice the #edit attribute: that means when the edit event is fired from component-one, run my "edit" method.
<template>
<div>
<p>{{ editItemNumber }}</p>
<component-two #edit="edit" />
</div>
<script>
export default {
data () {
return {
editItemNumber: null,
editBreakdownNumber: null
};
},
methods: {
edit(eventPayload) {
this.editItemNumber = eventPayload.item.editItemNumber
}
}
</script>
If you both C1 and C2 are children of the same parent P the idea is the same, except C1 can't listen directly to C2. Instead P will listen to the edit event and pass the needed changes down to C1 through its props.
The docs on components are really good, pay special attention to the sections on props and custom events.
https://v2.vuejs.org/v2/guide/components.html
I'm very confused about how to properly tie components together.
I have two components registered globally:
Vue.component('product-welcome-component', {
template: '#product-welcome-template',
props: ['showModal'],
onCreate(){
showModal = false;
}
});
Vue.component('product-create-modal-component', {
template: '#create-modal-template'
});
In the parent's template I included the child component like this:
<template id="product-welcome-template">
<div class="welcome-wrapper">
<div class="purpose-title"><h1 class="welcome-text">Welcome to Product Hub</h1></div>
<div class="purpose-create-btn"><button ##click="showModal = true" class="btn btn-primary btn-success create-btn">Create New Product</button></div>
<product-create-modal-component v-if="showModal"></product-create-modal-component>
</div>
</template>
The problem is (one of them) is that my create-modal-component is always showing, regardless of the value of showModal, in fact i can put in v-if="1 === 2" it would still show.
I'm sure this is not the right way of registering parent / child components but I can't seem to find a proper example. Mostly what i see that the parent is the app instance and it has a child of 'child' component and then they can communicate.
I have a feeling that including the child component in the parent's template is bad practice as it makes the parent strongly coupled.
Any help would be appreciated, thank you!
You are having showModal as props to product-welcome-component, but you are trying to set it false in created, but you have to use this in created to access showModal, like following:
Vue.component('product-welcome-component', {
template: '#product-welcome-template',
props: ['showModal'],
onCreate(){
this.showModal = false;
}
});
However you are saying product-create-modal-component shows even you do v-if="1 === 2", which should not be the case Can you create a fiddle of your case.