Reactjs: Key undefined when accessed as a prop - javascript

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

Related

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

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()
})
}

vue - $emit vs. reference for updating parent data

We need to use $emit to update the parent data in a vue component. This is what has been said everywhere, even vue documentation.
v-model and .sync both use $emit to update, so we count them $emit here
what I'm involved with is updating the parent data using reference type passing
If we send an object or array as prop to the child component and change it in the child component, changes will be made to the parent data directly.
There are components that we always use in a specific component and we are not going to use them anywhere else. In fact, these components are mostly used to make the app codes more readable and to lighten the components of the app.
passing reference type values as prop to children for directly change them from children is much easier than passing values then handle emitted event. especially when there are more nested components
code readability is even easier when we use reference type to update parent.
For example, suppose we have grand-parent, parent and child components. in parent component we have a field that change first property of grand-parent data and in child component we have another field that change second property of grand-parent data
If we want to implement this using $emit we have something like this : (we are not using .sync or v-model)
// grand-parent
<template>
<div>
<parent :fields="fields" #updateFields="fields = $event" >
</div>
</template>
<script>
import parent from "./parent"
export default {
components : {parent},
data(){
return {
fields : {
first : 'first-value',
second : 'second-value',
}
}
}
}
</script>
// parent
<template>
<div>
<input :value="fields.first" #input="updateFirstField" />
<child :fields="fields" #updateSecondField="updateSecondField" >
</div>
</template>
<script>
import child from "./child"
export default {
components : {child},
props : {
fields : Object,
},
methods : {
updateFirstField(event){
this.$emit('updateFields' , {...this.fields , first : event.target.value})
},
updateSecondField(value){
this.$emit('updateFields' , {...this.fields , second : value})
}
}
}
</script>
// child
<template>
<div>
<input :value="fields.first" #input="updateSecondField" />
</div>
</template>
<script>
export default {
props : {
fields : Object,
},
methods : {
updateFirstField(event){
this.$emit('updateSecondField' , event.target.value)
},
}
}
</script>
Yes, we can use .sync to make it easier or pass just field that we need to child. but this is basic example and if we have more fields and also we use all fields in all component this is the way we do this.
same thing using reference type will be like this :
// grand-parent
<template>
<div>
<parent :fields="fields" >
</div>
</template>
<script>
import parent from "./parent"
export default {
components : {parent},
data(){
return {
fields : {
first : 'first-value',
second : 'second-value',
}
}
}
}
</script>
// parent
<template>
<div>
<input v-model="fields.first" />
<child :fields="fields" >
</div>
</template>
<script>
import child from "./child"
export default {
components : {child},
props : {
fields : Object,
}
}
</script>
// child
<template>
<div>
<input v-model="fields.second" />
</div>
</template>
<script>
export default {
props : {
fields : Object,
}
}
</script>
as you see using reference type is much easier. even if there was more fields.
now my question :
should we use reference type for updating parent data or this is bad approach ?
even if we use a component always in the same parent again we should not use this method ?
what is the reason that we should not use reference type to update parent?
if we should not use reference type why vue pass same object to children and not clone them before passing ? (maybe for better performance ?)
The "always use $emit" rule isn't set in stone. There are pros and cons of either approach; you should do whatever makes your code easy to maintain and reason about.
For the situation you described, I think you have justified mutating the data directly.
When you have a single object with lots of properties and each property can be modified by a child component, then having the child component mutate each property itself is fine.
What would the alternative be? Emitting an event for each property update? Or emitting a single input event containing a copy of the object with a single property changed? That approach would result in lots of memory allocations (think of typing in a text field emitting a cloned object for each keypress). Having said that, though, some libraries are designed for this exact purpose and work pretty well (like Immutable.js).
For simple components that manage only small data like a textbox with a single string value, you should definitely use $emit. For more complex components with lots of data then sometimes it makes sense for the child component to share or own the data it is given. It becomes a part of the child component's contract that it will mutate the data in certain circumstances and in some particular way.
what is the reason that we should not use reference type to update parent?
The parent "owns" the data and it knows that nobody but itself will mutate it. No surprises.
The parent gets to decide whether or not to accept the mutation, and can even modify it on-the-fly.
You don't need a watcher to know when the data is changed.
The parent knows how the data is changed and what caused the change. Imagine there are multiple ways that the data can be mutated. The parent can easily know which mutation originated from a child component. If external code (i.e. inside a child component) can mutate the data at any time and for any reason, then it becomes much more difficult for the parent to know what caused the data to change (who changed it and why?).
if we should not use reference type why vue pass same object to children and not clone them before passing ? (maybe for better performance ?)
Well yes, for performance, but also many other reasons such as:
Cloning is non-trivial (Shallow? Deep? Should the prototype be copied too? Does it even make sense to clone the object? Is it a singleton?).
Cloning is expensive memory- and CPU-wise.
If it were cloned then doing what you describe here would be impossible. It would be silly to impose such a restrictive rule.
#Vue Detailed usage of $refs, $emit, $on:
$refs - parent component calls the methods of the child component. You can pass data.
$emit - child components call methods of the parent component and pass data.
$on - sibling components pass data to each other.

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

Vue2 passing arbitrary named variable as prop

I am new to Vue and after checking the docs I can not figure out how to achieve the following:
pass an arbitrarily named variable as a prop to a component instance.
From my understanding, props are meant to be a way to allow data to be passed to a component and as it states on the website:
Passing Data to Child Components with Props:
Props are custom attributes you can register on a component. When a value is passed to a prop attribute, it becomes a property on that component instance.
Since props can be required, it would seem that we can design components under the assumption that some data would be there, and possible within certain parameters (if the validator option is specified).
So I would like to define a function or object outside of vue, e.g. in an application, and pass this function or object to my vue instance.
This works if my named object of function has the exact same name as the prop to which I attempt to bind it. However, as I might have multiple instances of the Vue component and I might want to bind different data, I find using the same name for the variable less than ideal.
Now if I do as the Vue warning suggests, and name object / function the same as the prop, then the warning switches to that my data is not defined inside vue and to make sure it is reactive by reading: https://v2.vuejs.org/v2/guide/components-props.html
which, to be honest, doesnt really explain how to solve the issue,
or move the prop to the data level.
Which I can do (still gives the same warning), but kind of defeats the purpose of having props with my understanding of Vue.
This become more frustrating with anonymous vue instances.
e.g.
<script>
export default {
props: {
// records: {
// default: function(){return{}},
// type: Object
// }
},
data: function() {
return {
records: {} // define even an empty value in data for it to be 'reactive'
}
},
computed: {
fields: function() {
},
keys: function() {
return Object.keys(this.records)
}
},
methods: {
}
}
</script>
trying to use this as a component and set records to var myRecords = {"a": {}} fails:
<my-comp :records="myRecords"/>
So how exactly should I circumvent this? Where should I define my data then? and how should I handle the naming in the case of multiple instances?
A more fledged on example is found on a similar question:
Vue2: passing function as prop triggers warning that prop is already set
So I would like to define a function or object outside of vue, e.g. in an application, and pass this function or object to my vue instance.
It's hard to give a definitive answer because I don't know the specifics of how you have organized your code. Are you using Webpack? Single file components (.vue)? If yes to any of these, then you needn't use global variables in the way you have described in your question.
Your entire Vue app should consist of a single root Vue instance (which you instantiate with new Vue(...), and from there each component is rendered within the root component's template, and templates of those components, and so on.
Looking at the following template:
<my-comp :records="myRecords"/>
myRecords must be a property on the Vue component instance whose template contains the above. It could be declared within the data block, or as a computed property, or a prop, it doesn't matter.
Here's a small example:
<div id="app">
<my-comp :records="myRecords"></my-comp>
</div>
// Obtain records in some way and store it in a global variable
var records = ...
// This is the root Vue instance
new Vue({
el: '#app',
data: {
// You must store the records array in the Vue component like this
// for it to be referenced within the template.
// You can optionally transform the data first if you want.
myRecords: records.filter(r => r.name.startsWith('Bob'))
// ^ ^
// | |
// | +--- the global variable
// |
// +---- the name of the property on the component instance
}
})
Note that MyComp component does not access the records global variable in any way, it only takes its input through the records prop.

React's setProps() deprecated warning

In parent component App I render child component PersonsTable:
var App = React.createClass({
addPerson: function() {
// building personToAdd object and passing to personsTable...
this.refs.personsTable.setState({personToAdd: person});
},
render: function() {
return (
<div>
<PersonsTable ref="personsTable" />
...
</div>
);
},
});
Now I'm passing a new object by calling setState method of PersonsTable.
If I call setProps method, it returns a warning
Uncaught Invariant Violation: setProps(...): You called setProps on a component with a parent. This is an anti-pattern since props will get reactively updated when rendered. Instead, change the owner's render method to pass the correct value as props to the component where it is created.
How do I correctly pass such an object to PersonsTable without using setState method?
Here is full code https://jsfiddle.net/jpt5uy1k/
It looks like you're trying to implement an anti-pattern by calling setState on a ref. It's better to pass props to the child and use those. If you need state, set it in a parent and pass it down as a prop.
<div>
<PersonsTable aProp={someCoolInfoOrParentState} />
...
</div>
Refs should be used only when you need to "reach out" and get hold of an actual DOM node for one reason or another. Also, you generally want state in as few places as possible, since it can be complicated and requires extra "machinery" to make work. Better to keep state in a single place and thread it down through other components.
See this from https://github.com/reactjs/react-basic:
We tend to prefer our data model to be immutable. We thread functions through that can update state as a single atom at the top.

Categories

Resources