Generating a <select><option> dropdown (DD) from an array of objects. Added an id property to the objects to supposedly fix the dreaded "Each child in a list should have a unique 'key' prop" warning.
Sample data:
{id:70, city:"Allentown", state:"Pennsylvania"},
{id:71, city:"Alliance", state:"Ohio"},
{id:72, city:"Alliance", state:"Nebraska"},
Sample code:
<select name="cities" id="cities" onClick={selectCity}>
{cities.map((x) => (<option key={x.id} value={x.city}>{x.city}</option>))}
</select>
Note: The DD, and the array that populates it, is initially empty when the component loads. It's populated by a click event in another DD.
Spent a goodly chunk o' time trying out several SO suggestions. Tried wrapping the <select> in a <React.Fragment> block, and added a key to that. Tried using both 70 and '70' for the id property. Warning still appears. Using the array index as the key eliminates the warning, but I can't use that method because of other problems it causes.
Questions:
-- Data is coming from a JS file that I'm importing, is that a factor?
-- Is the array initially being empty on component load causing this?
-- What's the proper way to add the key prop to avoid the warning?
Thanks ahead of time for any help you awesome SO people can provide!
Answering my own question. In the initial post I asked "Is the array initially being empty on component load causing this?" The answer is yes, the empty array produces the warning.
When the component first loads, in map() it looks for a valid value for [array element].id. At this point, the array is empty, and has no ids loaded. It normally remains empty until an onClick event loads it with values. Had to add an initial default value to avoid the 'unique keys' warning. The fix is:
BAD (produces unique key warning):
const [cities, setCities] = useState([]);
GOOD (no warning): const [cities, setCities] = useState([{id: 0, city: '', state: ""}]);
Related
So I have the code below, with bookList_object being a bunch of JSON objects I fetched from backend.
_renderBookList = () => {
const books = this.state.bookList_object.map((this_book, index) => {
return <BookList
body={this_book.body}
created_at={this_book.created_at}
key = {index}
/>
})
Now, I've learned that there has to be key when having multiple children. So I've used index parameter to assign each id to the key prop. But then I get this error:
"BookList: 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."
What have I done wrong?
Also, I already have id field in each of my object. Is there a way to use id instead of key?
Thank you very much in advance. :)
I recently had this Warning. It happened because I tried to assign a key to some elements in two different parts of the code. The warning pops up when the key prop is accessed a second time.
Find out where else the component is being assigned a key. Then choose only one place to assign a key.
I have created the following demo to help me describe my question: https://codesandbox.io/s/dazzling-https-6ztj2
I have a form where I submit information and store it in a database. On another page, I retrieve this data, and set the checked property for the checkbox accordingly. This part works, in the demo this is represented by the dataFromAPI variable.
Now, the problem is that when I'd like to update the checkboxes, I get all sorts of errors and I don't know how to solve this. The ultimate goal is that I modify the form (either uncheck a checked box or vice versa) and send it off to the database - essentially this is an UPDATE operation, but again that's not visible in the demo.
Any suggestions?
Also note that I have simplified the demo, in the real app I'm working on I have multiple form elements and multiple values in the state.
I recommend you to work with an array of all the id's or whatever you want it to be your list's keys and "map" on the array like here https://reactjs.org/docs/lists-and-keys.html.
It also helps you to control each checkbox element as an item.
Neither your add or delete will work as it is.
Array.push returns the length of the new array, not the new array.
Array.splice returns a new array of the deleted items. And it mutates the original which you shouldn't do. We'll use filter instead.
Change your state setter to this:
// Since we are using the updater form of setState now, we need to persist the event.
e.persist();
setQuestion(prev => ({
...prev,
[e.target.name]: prev.topics.includes(e.target.value)
// Return false to remove the part of the array we don't want anymore
? prev.topics.filter((value) => value != e.target.value)
// Create a new array instead of mutating state
: [...prev.topics, e.target.value]
}));
As regard your example in the codesandbox you can get the expected result using the following snippet
//the idea here is if it exists then remove it otherwise add it to the array.
const handleChange = e => {
let x = data.topics.includes(e.target.value) ? data.topics.filter(item => item !== e.target.value): [...data.topics, e.target.value]
setQuestion({topics:x})
};
So you can get the idea and implement it in your actual application.
I noticed the problem with your code was that you changed the nature of question stored in state which makes it difficult to get the attribute topics when next react re-renders Also you were directly mutating the state. its best to alway use functional array manipulating methods are free from side effects like map, filter and reduce where possible.
I'm working with ReactJS on a project and using KnexJS to access a database. I would like to store and use the JSON objects I retrieve, in the state of a component as something along the lines of the following.
this.setState({tutors: tutors}) //Retrieval from database after promise
and then access their properties like so
this.state.tutors[0].email
this.state.tutors[0].firstname
However, when I try this, I get the following error
TypeError: Cannot read property 'email' of undefined
I have checked the console.log as well as using JSON.stringify to determine if the objects are retrieved by the time I need them. And it appears that they are. I simply cannot access their properties from state.
Is there something I can do about this? Or do I need to retrieve them from the DB one by one?
This could be happening, because at the moment your code tries to retrieve data from state, the data is not there yet -- though it's possibly added later. This is something that happens quite often, in my experience. You should probably check that an object exists, before trying to access a property on it -- and return null or undefined in case it doesn't exist:
this.state.tutors[0] ? this.state.tutors[0].email : null
EDIT (in response to the additional code samples):
Assuming your fetch function works ok and adds the fetched data to state (I would use forEach instead of map to push the elements into the array, or just map over the fetched array and add that to the state directly, without an intermediary array/variable/push -- but I think your code should work)...
The problem has to do with the render method, since when the component renders initially the data is not yet in state, and as such you're passing an empty array to the TutorTable component, which probably in turn produces the error you see.
The data gets added to state later, but at this stage the error on the initial render has already happened.
You could solve this by rendering the TutorTable conditionally, only when the data gets added to the state:
<div className="col-7">
<h3> My Tutors </h3>
{this.state.tutors.length > 0 && <TutorsTable tutors={this.state.tutors} />}
</div>
if you console.log out this.state.tutors in the render() method you should see in the console an empty array ([]) returned on the initial render, and then an array filled with data when the component re-renders when the data is added to the state.
I hope this helps.
It appears to me you are trying to get the first element of an array this.state.tutors[0].email
is the tutors really an array,could provide the format of the data that you console logged as you stated.This probably should be in the comment section but I have less than 50 rep so i cant comment.
you should check first tutors. if you are getting tutors an array then you can access its first element.
if(this.state.tutors[0]) {
console.log("first element of tutors array", this.state.tutors[0].email)
// anything you can acccess now of this element
}
and assign first tutors as an empty array in state.
state = {
tutors: []
}
in first lifecycle you have no data in this.state.tutors[0]
for this reason first check this.state.tutors[0] is ready or not like this
if(this.state.tutors[0])
{
var data=this.state.tutors[0];
data.push('your data');
this.setState({tutors:data})
}
first I should mention that I'm very new to react so this might be something silly..
I've struggled to get ReactTable's column to be sortable with the help of mattlockyer's gist
What I don't understand is I call setState on columns and then the render call is triggered correctly but it doesn't update the layout.
The render function "columns" contains an updated object with the correct column order, however render does not change the column order nor does sorting columns content.
This is the code in question,
this.setState({
columns: newColLayout,
}, () => {
this.mountEvents();
});
It will trigger a render, but the columns stay the same.
Ive put together a codepen with my current work and with comments about the code in question.
Any help would be greatly appreciated.
OK so i think i figured out whats going on.
The issue is splice modifies the state without triggering a change event, then when the event is fired from setStat, react doesn't see the change made to the column layout as it matches the current value.
The way i get around this is by cloning the column array using slice.
// Clone the current column state.
const columnClone = this.state.columns.slice(0);
// Remove item from array and stick it in a new position.
columnClone.splice(i, 0, columnClone.splice(this.draggedCol, 1)[0]);
Here is the updated code...
Still might be improved by someone who has more experience with react.
In earlier versions of React, I remember getting a warning when rendering an array without specifying a unique key for every item:
render() {
return (
<div>
{this.props.items.map(item => <span>{item.text}</span>)}
</div>
);
}
I understand that it is recommended to specify keys when rendering a dynamic list of items (where items can be added or removed) to help the reconciliation algorithm. I'd like to understand:
Why React doesn't warn anymore when keys are missing?
For static lists, is there a value in specifying item keys?
Official pointers will be appreciated.
Actually, I still see this warning message, and in official release notes there is no mention about this case
Example