Vue2 - Accessing to methods of a component with a generated ref name - javascript

I'm trying to access to a method of an child component, to validate multiple forms.
I have an array named "forms", each form object has a randomly generated id. Based on these form objects I generate components and give them a ref name
In my validateAll method I'm trying to loop over all forms, find components with their id as component's ref name. When I find the component (no problem so far), I try to call the child's method. But I get an error.
This is where I render components with v-for loop:
<SupportRequestForm v-for="(form, idx) in forms" :key="form.id"
...
:ref="`formRef${form.id}`"
/>
This is validateAll method, where I try to access child method:
validateAllForms() {
return this.forms.find(form => {
const formComponent = this.$refs[`formRef${form.id}`]
console.log(formComponent)
return formComponent.validateForm()
})
}
And this is the error:
I can access child component's method when it's a static ref name but I can't do the same thing when the is generated on the spot. Is this an expected behaviour or am I doing something wrong ?
Thank you very much in advance.

No need to bind each form to the form id, just create one ref called forms and since it's used with v-for it will contain the forms array :
When ref is used together with v-for, the ref you get will be an array containing the child components mirroring the data source
<SupportRequestForm v-for="(form, idx) in forms" :key="form.id" ref="forms" />
in method :
validateAllForms() {
this.$refs.forms.forEach(formComponent=> {
formComponent.validateForm()
})
}

Related

Vue.js reactivity of data object property not triggered when using v-model

I have a component with data containing an object, this object has a property which is an array.
<template>
<div>
<product-selector :selected-products="order.selectedProducts" v-model="order.selectedProducts"/>
</div>
</template>
data () {
return {
order: {
selectedProducts: [],
},
};
}
Everytime a new product is selected/deselected in product-selector, I emit an "input" event with the new array. The problem is that the order object in the parent component is not reactive and is not triggering the new rendering events. If I don't use an "order" object but directly a "selectedProducts" array it works, but I don't want to use this solution. Later I need to pass the order object to other components.
You can make your object like:
order: {
selectedProducts: [],
key:0
},
and your template
<product-selector :selected-products="order.selectedProducts" v-model="order" :key="order.key/>
and when you emit the new selected products increment the order key
If you want to make the components 'reactive', you have to emit data up from the child component to the parent component, and then in the parent component, you have to react to the emitted events.
You can read more about it in the documentation here:
https://v2.vuejs.org/v2/guide/components.html#Using-v-model-on-Components
Pay attention to this part:
For this to actually work though, the inside the component
must:
Bind the value attribute to a value prop On input, emit its own custom
input event with the new value

How to access and render a component in an array dynamically, based on a selected index?

I have an array of objects that have a key Component, which points to its respective component.
I'm able to map over the array and display all components like this,
dict.map(({ Component }) => <Component />
And that works, however I'm trying to conditionally render specific components. In my case, I'm trying to access the Component property at index 1, however it doesn't seem to work. The way I'm trying to access it is something like, dict[displayComponentAtIndex].Component, where displayComponentAtIndex is part of the state.
How can I render the component the way I want to?
You should change your dict.js to
export default [{ Component: (<Component1></Component1>) }, { Component: <Component2></Component2> }];
As you need component and not object to render.

Why key is null?, despite I created it

I created a list based in an array using a custom component, and it works, but not properly, because I did try to send a 'key' prop in the component implementation, but inside the component, key prop doesn't exist, is just undefined.
The expected behaviour is to access to the 'key' prop inside the component, and avoid the "key is not a prop" warning on my app.
This is the data
data: [
{ key: 1, definition: "Def 1", example: "Example text 1" },
{ key: 2, definition: "Def 2", example: "Example text 2" },
{ key: 3, definition: "Def 3", example: "Example text 3" }
]
This is the component implementation
createDefinitions = (data) => {
return data.map((item, index) => {
return <Definition {...item}/>
})
}
'item' is an object with every duple in the previous array. Actually, I tried to add 'key' prop directly in this point, but it was the same.
Inside the custom component I tried to print the props object, but 'key' doesn't exist
<Text>{JSON.stringify(props)}</Text>
You can the code here https://snack.expo.io/r1VE3DiQV
The expected behaviour is to get key property inside custom component.
Thanks!
tl;dr you cannot access a key via this.props.key. If you need the value that you set for the key pass it via a different name such as id.
You cannot access the key property via props once you have set it. If you try to access it you will get the following yellow warning
Warning: %s: key is not a prop. Trying to access it will result in
undefined being returned. If you need to access the same value
within the child component, you should pass it as a different prop.
https://reactjs.org/warnings/special-props.html
I've used the actual url that the link resolves to as SO complains about the use of shortened urls
Following the link in the warning gives you the following message:
Most props on a JSX element are passed on to the component, however,
there are two special props (ref and key) which are used by React, and
are thus not forwarded to the component.
It then goes on to tell you that if you require access to the value you should set it as a different prop.
For instance, attempting to access this.props.key from a component
(i.e., the render function or propTypes) is not defined. If you need
to access the same value within the child component, you should pass
it as a different prop (ex:
<ListItemWrapper key={result.id} id={result.id} />). While this may seem redundant, it’s important to
separate app logic from reconciling hints.
Looking at your code it, is working fine, the only issue is that you are trying to access the key which isn't passed via props. If you remove the references to props.key in your Definition component the warning should go.
You are creating a list of elements by mapping over your data, and in that case you need to add a key prop (with a string value) to each list element. This is completely unrelated to the data object you are using, but you could use that key as prop value. So in this case you need to add a key prop to Definition:
createDefinitions = (data) => {
return data.map((item, index) => {
return <Definition key={item.key.toString()} {...item}/>
})
}
Alternatively, you could also use the index if you don't have unique keys in your data

How to isolate data from Vue component in JS class?

I have a simple JS class where in the constructor I initialize dictionary. I have methods to update or delete items from this dictionary.
export default class Items {
constructor() {
this.items = {};
//Code that adds some values to it
}
save(item) {
this.items[item.id] = item;
}
delete(itemId) {
delete this.items[itemId];
}
}
And I have a Vue component where I simply output values from this dictionary with
<p v-for="item in items">{{item}}</p>
In mounted method, I create Items object and assign items dictionary to variable items from Vue data. And from Vue I call methods save or delete, I see that dictionary items in Vue data is being changed, but UI doesn't get updated. I know that I can use Vue.$delete or Vue.$set but is it possible to isolate simple JS class from Vue and keep all the data in JS class?
I'm using Vue 2.4.4
Try to use power of VueJs
...
save(item) {
Vue.set(this.items, item.id, item);
}
delete(itemId) {
let index = this.items.findIndex(p => p.id === itemId);
Vue.delete(this.items, index);
}
Is there a reason to not let Vue keep the data? It will be able to monitor it to make the most effective dom changes that way. So in proper Vue style, you should move the class logic to the component level so that that data returns the items dictionary. Then attach the save and delete as methods for the component.
For objects you only get reactivity when you edit keys that existed at render time, otherwise, you need to alert Vue a key is being added. This is done with Vue.set(this.items, item.id, item);.
If you want to separate the logic from the component you can look into vuex which allows you to create modules of state and mutations.

Reactjs: Key undefined when accessed as a prop

Tools: Reactjs 0.14.0 Vanilla Flux
I need unique identifiers for 2 reasons:
Child Reconciliation
Keeping track of what child was clicked
So let's say I have a list of messages that looks like this:
[
{
id: 1241241234, // <-----The unique id is kept here
authorName: "Nick"
text: "Hi!"
},
...
]
And now I use a Array.prototype.map() to create "ownee" component (MessageListItem) inside of the owner component MessageSection
function getMessageListItem(message) {
return (
<MessageListItem key={message.id} message={message} />
);
}
var MessageSection = React.createClass({
render: function() {
var messageListItems = this.state.messages.map(getMessageListItem);
<div>
{messageListItems }
</div>
}
});
But the this.props.key is undefined in the MessageListItem even though I know for a fact that is was defined when it was passed down.
var ConvoListItem = React.createClass({
render: function() {
console.log(this.props.key); // Undefined
}
});
I'm guessing there is a reason that React is not letting key be used as a prop.
Question:
If I can't use key as a prop, then what is the proper way to handle the duality need of keying and setting unique identifiers on a dynamic list of child elements that contain state?
key and ref aren't really 'props'. They're used internally by react and not passed to components as props. Consider passing it as a prop such as 'id'.
It is best to use id. Then in the eventHandler you can have event.target.id.
function getMessageListItem(message) {
return (
<MessageListItem key={message.id} id={message.id} message={message}/>
);
}
As we already established in other answers that you can't use key since it isn't passed as a prop and instead used internally by react. Here is my 2 cents as an alternative:
Since you're passing the entire array of messages as a prop while creating the <MessageListItem> component, you don't necessarily need to pass another prop with id. You can simply use {this.props.message.id} whenever you need to use id.
As you've discovered, the key property is consumed by React itself, not passed to the child component. It's what React uses to keep track of entries in arrays to avoid unnecessarily re-rendering them (both in terms of calling their render and reconciling with the DOM).
Unfortunately, that means if you also want to use it in the child, you have to pass it twice. In your code, you already are passing it twice (once as key, one as part of message). So you can keep your current JSX, just use id from message in the child:
var MessageListItem = React.createClass({
render: function() {
console.log(this.props.message.id);
// ^^^^^^^^^^^−−−−− Has the value
}
});
More:
Lists and Keys
Why keys are necessary (linked from the above)
Special props warning - which tells us that key and ref are special props not passed on to the child component

Categories

Resources