I am having this problem which I can not solve. So basically I have 2 components here Component A and Component G I have more of them but they are pretty much the same(concept is the same). I have a Link in Component G, A which when I click it, it brings me to the Payment Component. Tt now renders the (ImageA, ClassA and PriceA) its hard coded. Anyway what I am trying to achieve is that when I click for example on the Component G it renders only that (imageG, classG and priceG), so I would need it to filter it somehow or split the data somehow in the json file I imagine, but I dont know how to do it. I hope one of you fellow comrades will help me.
Payment Component
const Payment = ({
history,
location: { state },
match: {
params: { dataId }
}
}) => {
if (!state?.postData) {
history.goBack();
}
const { postData } = state;
return (
<div className="ml-20">
<img
alt=""
className="w:2/4 object-contain "
src={postData.imageG}
/>
<h2
className=" ml-24 mlg:ml-6 mlg:mt-2 mlg:static text-5xl mlg:text-2xl text-blue-400 absolute top-
48" >
{postData.classG}
</h2>
<h3
className="text-lg mlg:mb-2 mlg:ml-6 mlg:mt-2 mlg:static font-bold text-green-800
font-mono ml-24 top-64 absolute"
>
{postData.priceG}
</h3>
</div>
);
};
export default Payment
A COMPONENT
import React from "react";
import data from "../data.json";
import { Link } from "react-router-dom";
function A() {
return (
<div className='bg-black '>
{data.filter(d =>
d.classA &&
d.imageA &&
d.priceA)
.map((postData) => {
return (
<div className='bg-black' key={postData.id} >
<div className='bg-black '>
<img
alt=""
className="w-full object-contain "
src={postData.imageA}
></img>
<h1 className=" ml-24 mlg:ml-6 mlg:mt-2 mlg:static text-5xl mlg:text-2xl text-red-600
absolute top-48">
{postData.classA}
</h1>
<h1 className="text-lg mlg:mb-2 mlg:ml-6 mlg:mt-2 mlg:static font-bold text-yellow-600
font-mono ml-24 top-64 absolute" >
{postData.priceA}
</h1>
<Link
to={{pathname: `/payment/${postData.id}`,
state: {
postData,
},
}}
className="py-1 px-2 mlg:ml-6 mlg:mt-14 mlg:static text-black-600 h-8 ml-24 top-72 bg-
white w-32 text-center text-gray-red
rounded-full focus:outline-none focus:ring-2 focus:ring-gray-600 absolute"
>
Buy Now
</Link>
</div>
</div>
</div>
)
}
export default A
G Component
import React from "react";
import data from "../data.json";
import { Link } from "react-router-dom";
function G() {
return (
<div className='bg-black '>
{data.filter(d =>
d.classG &&
d.imageG &&
d.priceG)
.map((postData) => {
return (
<div className='bg-black' key={postData.id} >
<div className='bg-black '>
<img
alt=""
className="w-full object-contain "
src={postData.imageG}
></img>
<h1 className=" ml-24 mlg:ml-6 mlg:mt-2 mlg:static text-5xl mlg:text-2xl text-red-600
absolute top-48">
{postData.classG}
</h1>
<h1 className="text-lg mlg:mb-2 mlg:ml-6 mlg:mt-2 mlg:static font-bold text-yellow-600
font-mono ml-24 top-64 absolute" >
{postData.priceG}
</h1>
<Link
to={{pathname: `/payment/${postData.id}`,
state: {
postData,
},
}}
className="py-1 px-2 mlg:ml-6 mlg:mt-14 mlg:static text-black-600 h-8 ml-24 top-72 bg-
white w-32 text-center text-gray-red
rounded-full focus:outline-none focus:ring-2 focus:ring-gray-600 absolute"
>
Buy Now
</Link>
</div>
</div>
</div>
)
}
export default G
Json file
[
{
"id":0,
"classG":"G-Class",
"imageG":"./ModImages/Gclass.jpg",
"priceG":"Starting at $154,900",
"newG":"A 12.3-inch digital gauge cluster is newly standard across the
board, as are heated front seats. Plus, the sedan can be had with a Night
package that adds black exterior trim.",
"imageGIn":"./ModImages/GclassIn.jpg",
"imageCOut":"./ModImages/GclassOut.jpg",
"infoDesignC1":"How many technological breakthroughs can you fit into a C-
Class? With the advanced, powerful and sporty 2020 C 300 Wagon, plenty. From
its LED headlamps to its generous cargo hold, you'll find countless
refinements, and so many reasons it's a wagon like no other.",
"infoDesignG" :"The acclaimed C-Class cabin is as functional as it is
beautiful. Abundant innovations harmonize with five options in hand-finished
wood trim. A 12.3-inch digital instrument cluster and 10.25-inch centre
screen are available.",
"imageGIn1":"./ModImages/CclassIn1.jpg",
"imageGOut1":"./ModImages/CclassOut1.jpg",
"infoDesign2": "The Star on the outside is earned on the inside. The roomy,
refined cabin includes heated front seats and a driver-seat memory. Dual-
zone, double-filtered climate control. A panoramic sunroof. And options from
Burmester® surround sound to new Climate Comfort front seats.",
"infoInov": "Available Apple CarPlay™ and Android Auto let you enjoy more
from your own smartphone. Another navigation option is COMAND, which brings
you voice control not only for navigation and audio, but also many cabin
features.",
"imageInov1":"./ModImages/GclassInov1.jpg",
"imageInov2":"./ModImages/GclassInov2.jpg",
"infoInov1": "Available wireless charging lets you refuel your compatible
phone's battery as you drive. Easy-connect NFC pairing links your phone
every
time you get in. Apple CarPlay™ and Android Auto are available, too. And
four USB-C ports are standard.",
"infoInov2": "DYNAMIC SELECT lets you fine-tune your A-Class with the tap of
a console button. Four modes vary shift points, throttle response, steering
feel and more: efficient ECO, everyday Comfort, sharpened Sport, and create-
your-own Individual.",
"imageInov":"./ModImages/GclassInov.jpg",
"imageInov4":"./ModImages/GclassInov4.jpg" ,
"infoInov4G": "From the everyday to the unexpected, Mercedes me connect helps
ease your way. You can control vehicle features from your smartphone
(including Remote Start), set up a service appointment, and more. You can
also add In-car Wi-Fi for a low monthly rate. ",
"infoInov5C":"Available driver assists help de-stress driving, changing lanes
with a tap of your finger, and knowing to slow for a tollboth or exit. A
network of sensors keeps a constant lookout for danger ahead, even if it's
coming from behind."
},
{
"id":1,
"classA":"A-Class",
"imageA":"./ModImages/Aclass.jpg",
"priceA":"Starting at $54,900",
"newA":"In addition to a new 18-inch wheel design, the 2021 A-class gains
standard blind-spot monitoring and an optional gesture control feature for
the MBUX infotainment system.",
"imageAIn":"./ModImages/AclassIn.jpg",
"imageAOut":"./ModImages/AclassOut.jpg",
"infoDesignA":"A is for attention-getting. The clean lines, LED lighting and
aggressive stance of the A-Class Sedan are designed to capture admiring eyes.
Its ultramodern cabin and premium appointments aim to captivate its driver
and passengers for years to come.",
"infoDesignA1" :"With 100% LED lighting standard, inside and out, the A-Class
shines day or night, coming or going, and staying, too. Options include
MULTIBEAM LED headlamps and 64-colour ambient cabin lighting.",
"imageAIn1":"./ModImages/AclassIn1.jpg",
"imageAOut1":"./ModImages/AclassOut1.jpg",
"infoDesignA2": "The Star on the outside is earned on the inside. The roomy,
refined cabin includes heated front seats and a driver-seat memory. Dual-
zone, double-filtered climate control. A panoramic sunroof. And options from
Burmester® surround sound to new Climate Comfort front seats.",
"infoInov": "A is for advanced. With the Mercedes-Benz User Experience
(MBUX), the A-Class drives a new generation of user-friendly tech. Quite
possibly the most capable, natural and intuitive speech interface from any
automaker, it's easy to learn because it learns you.",
"imageInov1":"./ModImages/AclassInov1.jpg",
"imageInov2":"./ModImages/AclassInov2.jpg",
"infoInov1": "Available wireless charging lets you refuel your compatible
phone's battery as you drive. Easy-connect NFC pairing links your phone
every time you get in. Apple CarPlay™ and Android Auto are available, too.
And four USB-C ports are standard. Disclaimer[6] Disclaimer",
"infoInov2": "DYNAMIC SELECT lets you fine-tune your A-Class with the tap of
a console button. Four modes vary shift points, throttle response, steering
feel and more: efficient ECO, everyday Comfort, sharpened Sport, and create-
your-own Individual.",
"imageInov3":"./ModImages/AclassInov3.jpg",
"imageInov4":"./ModImages/AclassInov4.jpg" ,
"infoInov4": "Get up close with the A-Class Sedan in this captivating ASMR audio experience. "
}
]
I'm updating the answer based on the comments
First thing, I would make sure my JSON properties are consistent across objects:
[
{
"id":0,
"class":"G-Class",
"image":"./ModImages/Gclass.jpg",
"price":"Starting at $154,900",
"new":"Mercedes-Benz introduces a mid-cycle update of the E-class lineup for 2021. The changes
apply",
"infoInov": "....",
"imageOut": "..."
},
{
"id":1,
"class":"A-Class",
"image":"./ModImages/Aclass.jpg",
"price":"Starting at $54,900",
"new":"Mercedes-Benz introduces a mid-cycle update of the E-class lineup for 2021. The changes
apply",
"infoInov": "....",
"imageOut": "..."
}
]
Since all the properties are consistent, the only way you can consistently tell them apart is by the id property. If id === 0, we know it's the G. if === 1, we know it's A, and so on.
This way, I can use only one component to render them both. If you want to apply different styles or render something if it's component G, you can do something like:
function AG() {
return (
<div className="bg-black">
{data.map(product => (
<div>
<img src={product.image} />
<h1>{product.class}</h1>
<h1 className={product.id === 0 ? 'g-class' : ''}>{product.price}</h1>
<Link to={`/payment/${product.id}` />
{product.id === 0 && <div>This will only appear to G</div>}
</div>
))}
</div>
)
}
Notice above that I have some checks to apply styles or render something only if product.id === 0. This is one of the ways to reuse a component and apply little changes to it.
In the <Link> we are sending the user to /payments passing the productID in the path. So inside the Payments component, you would use this productID to filter the data from the JSON.
Now in the Payment component, you can just import the same JSON data and filter by the id we are passing in the URL path:
import data from "../data.json"
const Payment = () => {
// get the id from the URL somehow, I suppose you are using react router
// this is just an example
const { idFromURL } = match.param
const filteredProduct = data.filter(product => product.id === idFromURL)
return (
<div>
<img src={filteredProduct.image} />
</div>
)
}
Now the filteredProduct variable has the data for either A or G, and you can use it to display the specific data you want, inside the Payment component.
Related
I have a React app that mapping cards and each card have unique id although I'm getting error in console that some are not unique:
Warning: Encountered two children with the same key, 2294264. Keys
should be unique so that components maintain their identity across
updates. Non-unique keys may cause children to be duplicated and/or
omitted — the behavior is unsupported and could change in a future
version.
Here is the code for building my card structure:
function CreateCards(doc) {
return (
<SimpleCard
key={doc.newsid}
theCardId={doc.newsid}
categorietitle={doc.categorietitle}
newstitle={doc.newstitle}
date={format(new Date(doc.date), "dd/MM/yyyy")}
thenews={doc.thenews}
newsurl={doc.newsurl}
/>
);
}
And here is the code for mapping the cards:
<div className="general-card1">
{this.state.noPlaceFound ? (
<h3 className="noPlaceFound">
<i className="fas fa-exclamation-circle fa-sm WarnIcon"></i>
لا يوجد نتائج
</h3>
) : (
this.state.WasMap.map((v) => CreateCards(v._source))
)}
</div>
Can you please help me?
When you render a list of components with map(), each component should have a unique key property. It is for React to distinguish them.
Rendering a todo list, for example:
{todos.map((todo) => (<li key={todo.id}>{todo.text}</li>))}
What you did wrong is that you added the key inside the component, where it should be when rendering inside map(), like so:
<div className="general-card1">
{this.state.noPlaceFound ? (
<h3 className="noPlaceFound">
<i className="fas fa-exclamation-circle fa-sm WarnIcon"></i> لا يوجد نتائج
</h3>
) : (
this.state.WasMap.map((v, index) => (<CreateCards doc={v._source} key={index} />))
)}
</div>
Notice that key = {index}. This would work, but it's better to use a custom id if you have one. You can use key = {v._source.newsid} if v._source.newsid is a unique field.
For more visit
keys section on the official documentation.
I have a weird problem. In my React project I have a useState([]) variable containing information. On the website I have a "add" button that adds a new price-object to the array.
This is then visually shown on the website with map. Each section (object in the array) has a delete-button. When I delete a section it only delete the newly added object. BUT, when I console.log() the updated values, it have deleted right object.
So, to summarize. In the stored values, it deletes correct, but it won't show it right visually. Someone who may be able to help?
Here is the code:
My .map() code, with the code for deleting a section at the bottom:
{courseData.prices?.map((price, i) => {
return (
<div
className='flex flex-wrap bg-white mb-4 p-2 rounded-md shadow-md'
key={i}>
<Input
label='Navn'
value={price.name}
width='1/2'
white
isRequired
handleChange={(e) => updatePrice(e, i, 'name')}
/>
<Input
label='Beskrivelse'
value={price.description}
width='1/2'
white
isRequired
handleChange={(e) => updatePrice(e, i, 'description')}
/>
<Input
label='Pris'
type='number'
width='1/2'
value={price.price}
white
isRequired
handleChange={(e) => updatePrice(e, i, 'price')}
/>
<Input
label='Gyldighet (dager)'
type='number'
width='1/2'
value={price.days}
white
isRequired
handleChange={(e) => updatePrice(e, i, 'days')}
/>
<button
type='button'
onClick={() => {
setCourseData((courseData) => {
return {
...courseData,
prices: courseData.prices.filter((item) => item !== price),
}
})
}}
className='flex items-center min-w-max rounded-md px-2 py-1 m-1 bg-white-light shadow-md font-semibold text-gray-500 active:shadow-none'>
<XIcon className='h-4 w-4' />
<p className='font-medium ml-1'>Fjern</p>
</button>
</div>
)
})}
A picture of the visual components before something is deleted:
A picture of the visual components after number 2 (the middle one) is deleted:
The stored data after the middle one is deleted (you see that the stored data and the visual components dont have the same names):
I tried to recreate the issue here, but everything is working as expected.
I'm guessing that there is some bad data causing this issue, but I also agree with what was suggested in the comments: compare attributes of the objects instead of comparing the objects themselves.
Use this:
setCourseData(courseData => {
return {
...courseData,
prices: courseData.prices.filter(item => item.id !== price.id)
};
instead of this:
setCourseData(courseData => {
return {
...courseData,
prices: courseData.prices.filter(item => item !== price)
};
If this doesn't fix the issue, could you share more of the data?
I am very new to react and am trying to make a user survey, in this Component I am working with checkboxes which I find a little tricky. I want the user to choose 2 options and then put a comma between the two chosen options, how can I do that?
Earlier I used stageArtCategory.join(', ') in my Summary.js component but that broke the rendering of Summary in case the user chose only one instead of two options...
CODE:
import React from 'react';
import NextQuestionButton from './NextQuestionButton'
import Popup from './Popup'
const QuestionStageArtCheckbox = ({
stageArtCategory,
setStageArtCategory,
page,
setPage,
onNextQuestion
}) => {
const onStageArtChange = (stageArtValue) => {
if (stageArtCategory.includes(stageArtValue)) {
setStageArtCategory(stageArtCategory.filter((item) => item !== stageArtValue))
} else {
setStageArtCategory([...stageArtCategory, stageArtValue])
}
}
const stageartGroup = [
"I'm for experimental shit, I need to see something I can not immediately understand",
"I want to dance with my kid",
"Opera",
"Theatre",
"Musical",
"Concert",
"I just miss the feeling of collectivity, and long to be able to see something with another person's eye - just once, please!",
"I want to drink beer with my friends after a show"
]
return (
<article className="form-question-3">
{/* Q */}
<p className="form-question" tabIndex="0">
What kind of stage art would you like too experience post Covid-19? Pick 2 as they are made to overlap a little! <span role="img" aria-label="smiling emoji with one eye blinking">😊</span>
</p>
{/* A */}
<div className="question-content-container-3">
{stageartGroup.map((stagearts) => (
<span className="form-checkbox-question-container" key={stagearts}>
<label className="checkbox-label" htmlFor={stagearts}>{stagearts}</label>
<input
id={stagearts}
type="checkbox"
className="form-checkbox"
checked={stageArtCategory.includes(stagearts)}
onChange={() => onStageArtChange(stagearts)}
/>
</span>
))}
<div className="buttons-container-3">
<NextQuestionButton
page={page}
setPage={setPage}
currentState={stageArtCategory.length}
defaultState={0}
message="Please choose what kind of overlapping stage arts you would like to experience first!"
onClick={onNextQuestion}
button="Next question button"
buttontext="Next question"
/>
</div>
</div>
</article>
)
}
export default QuestionStageArtCheckbox
I prefer to always treat such values as an arrays. So, whenever I need to use such value, I turn it into array with .concat method.
function arbitraryFunction(arrayOrSingle) {
let array = [].concat(arrayOrSingle);
// do something with array
}
Initialised the useState as an empty string earlier but changed it to be an empty array. This solved the problem.
const [stageArtCategory, setStageArtCategory] = useState([])
I wrote a function in one of my angular project to replace tag with another tag in xml file. First parameter is the HTMLElement, second parameter is the searchTag and third one is replaceTag.
replaceTagWith(xmlCode: HTMLElement, findTag: string, replaceTag: string) {
// Initializing variables
let exists: boolean = true;
let processed: boolean = false;
while (exists) {
let sourceTags = xmlCode.querySelector(findTag);
// If findTag exists
if (sourceTags) {
// Setting processed flag
processed = true;
// Creating replace tag and setting the textcontent
let targetTag = document.createElement(replaceTag);
targetTag.innerHTML = sourceTags.innerHTML;
// Finding attributes of the tags to be replaced
let tagAttributes = sourceTags.attributes;
let attrLength = tagAttributes.length;
// If there is attributes
if (attrLength) {
for (let j = 0; j < attrLength; j++) {
// Setting the attribute of replace tag
targetTag.setAttribute(tagAttributes[j].name, tagAttributes[j].value);
}
}
// Appending the target tag just before the source tag
sourceTags.parentNode.insertBefore(targetTag, sourceTags);
// Removing findTag
sourceTags.parentNode.removeChild(sourceTags);
} else {
exists = false;
}
}
This function works well when i am replacing <title> tag with <sec-title>. Here is the function call..
const parser = new DOMParser();
const xml = parser.parseFromString(this.xmlData, 'application/xml');
let xmlCode: any = xml.documentElement;
this.replaceTagWith(xmlCode, 'title', 'sec-title');
the result will be like:
<sec-title xmlns="http://www.w3.org/1999/xhtml">What is Child Protection<named-content xmlns="http://www.w3.org/1999/xhtml" content-type="index" name="default" a-level="child protection" b-level="definition of" c-level="" d-level="" e-level="" format="" range="" see="" see-also=""></named-content> in Humanitarian Action? </sec-title>
But the problem is that when i try to replace <sec-title> with <title> ,the result showing:
<title xmlns="http://www.w3.org/1999/xhtml">What is Child Protection<named-content xmlns="http://www.w3.org/1999/xhtml" content-type="index" name="default" a-level="child protection" b-level="definition of" c-level="" d-level="" e-level="" format="" range="" see="" see-also=""></named-content> in Humanitarian Action? </title>
the html tags (here <named-content>) inside the <title> get replaced with <named-content>
This is the portion of xml I am working with..
<sec level="2" id="sec002" sec-type="level-B">
<title xmlns="http://www.w3.org/1999/xhtml">What is Child Protection<named-content content-type="index"
name="default" a-level="child protection" b-level="definition of" c-level="" d-level="" e-level=""
format="" range="" see="" see-also=""></named-content> in Humanitarian Action? </title>
<p specific-use="para">Child protection is the ‘prevention of and response to abuse, neglect, exploitation and
violence against children’.</p>
<p specific-use="para">The objectives of humanitarian action are to:</p>
<list list-type="bullet" specific-use="1" list-content="Lijstalinea">
<list-item>
<p specific-use="para">Save lives, alleviate suffering and maintain human dignity during and after
disasters; and</p>
</list-item>
<list-item>
<p specific-use="para">Strengthen preparedness for any future crises.</p>
</list-item>
</list>
<p specific-use="para">Humanitarian crises<named-content xmlns="http://www.w3.org/1999/xhtml"
content-type="index" name="default" a-level="humanitarian crises" b-level="causes of" c-level=""
d-level="" e-level="" format="" range="" see="" see-also=""></named-content> can be caused by humans,
such as conflict or civil unrest; they can result from disasters, such as floods and earthquakes; or they
can be a combination of both.</p>
<p specific-use="para">Humanitarian crises<named-content xmlns="http://www.w3.org/1999/xhtml"
content-type="index" name="default" a-level="humanitarian crises" b-level="effects on children of"
c-level="" d-level="" e-level="" format="" range="" see="" see-also=""></named-content>
often have long-lasting, devastating effects on children’s lives. The child protection risks children face
include family separation, recruitment into armed forces or groups, physical or sexual abuse, psychosocial
distress or mental disorders, economic exploitation, injury and even death. They depend on factors such as
the:</p>
<list list-type="bullet" specific-use="1" list-content="Lijstalinea">
<list-item>
<p specific-use="para">Nature and scale of the emergency;</p>
</list-item>
<list-item>
<p specific-use="para">Number of children affected;</p>
</list-item>
<list-item>
<p specific-use="para">Sociocultural norms;</p>
</list-item>
<list-item>
<p specific-use="para">Pre-existing child protection risks;</p>
</list-item>
<list-item>
<p specific-use="para">Community-level preparedness; and</p>
</list-item>
<list-item>
<p specific-use="para">Stability and capacity of the State before and during the crisis.</p>
</list-item>
</list>
<p specific-use="para">Child protection actors and interventions seek to prevent and respond to all forms of
abuse, neglect, exploitation and violence. Effective child protection builds on existing capacities and
strengthens preparedness before a crisis occurs. During humanitarian crises, timely interventions support
the physical and emotional health, dignity and well-being of children, families and communities.</p>
<p specific-use="para">Child protection in humanitarian action includes specific activities conducted by local,
national and international child protection actors. It also includes efforts of non-child protection actors
who seek to prevent and address abuse, neglect, exploitation and violence against children in humanitarian
settings, whether through mainstreamed or integrated programming.</p>
<p specific-use="para">Child Protection in Humanitarian Action promotes the well-being and healthy development
of children and saves lives.</p>
</sec>
To display this code in broser i first replaced the <title> tag with <sec-title> and it works fine. Finally at the stage of exporting xml i replaced back <sec-title> with <title>. Here is the stage where the problem occurring...
I have the following component where I read data from Indexeddb (ArrayBuffer) and use it as source for an image. When the parent component uses this component, only the last formUpload gets it's dataSource set. My console.log's tells me that the data is there, the URL.createobjecturl is succesfully created, I can open them in devtools and see the images, but they are not assigned as the source of the image. Any ideas?
<div class="ui grid segment" v-if="formData.Uploads.length > 0">
<div class="four wide column" v-for="upload in formData.Uploads" style="position:relative;">
<formUpload :upload="upload"></formUpload>
</div>
</div>
Vue.component("formUpload", {
props: ["upload"],
template: `
<div class="ui fluid image">
<a class="ui green left corner label">
<i class="add icon"></i>
</a>
<a class="ui red right corner label" v-on:click="removeUpload(upload)">
<i class="remove icon"></i>
</a>
<img style="width:100%;" :src="dataSource" />
<div class="ui blue labels" v-if="upload.tags.length > 0" style="margin-top:5px;">
<image-tag v-for="tag in upload.tags" :tagtext="tag" :key="tag" :upload="upload.id" v-on:deletetag="deletetag"></image-tag>
</div>
</div>
`,
data: function () {
return {
dataSource: undefined
}
},
mounted: function () {
_this = this;
imageStore.getItem(_this.upload.imageId).then(function (result) {
console.log("Data gotten", result.data);
var dataView = new DataView(result.data);
var blob = new Blob([dataView], { type: result.type });
console.log("Data transformed", blob);
_this.dataSource = URL.createObjectURL(blob);
console.log("DataUrl", _this.dataSource);
});
},
methods: {
removeUpload: function (upload) {
console.log("removeUpload");
}
}
});
In short, you need a key.
When Vue is updating a list of elements rendered with v-for, it by
default uses an “in-place patch” strategy. If the order of the data
items has changed, instead of moving the DOM elements to match the
order of the items, Vue will simply patch each element in-place and
make sure it reflects what should be rendered at that particular
index. This is similar to the behavior of track-by="$index" in Vue
1.x.
This default mode is efficient, but only suitable when your list render output does not rely on child component state or temporary DOM
state (e.g. form input values).
[...]
It is recommended to provide a key with v-for whenever possible, unless the iterated DOM content is simple, or you are intentionally relying on the default behavior for performance gains.
This is a fairly common "gotcha" which should be helped by the fact that
In 2.2.0+, when using v-for with a component, a key is now required.