Reputation: 45
I am trying to set a key for each component generated by a v-for
loop.
I would like to click on a button and generate a row of Bootstrap columns after every click.
I went for a v-for
approach despite having no array to loop over, so I suppose a key is required as otherwise, an error would pop up.
This is the row before I click on the submit button :
And the result after I click :
My goal is to generate a new row with empty cards with original styling. Right now it seems that it is copying the state of the previous row.
App.vue:
<ul id="guessList" class="list-unstyled">
<li v-for="guess in guessIDs" :key="guess">
<div :id="guess" class="row justify-content-center align-self-center mb-3">
<MoveGroup v-for="n in lengthGuess"
:key="`${guess}_move_group${n}`">{{ n }}</MoveGroup>
</div>
</li>
</ul>
I don't understand why this code doesn't generate a unique key every time. The guess part of the key corresponds to the element taken from an array containing strings representing IDs.
Here's the vue app :
App.vue:
export default {
name: 'boardApp',
data() {
return {
lengthGuess: 0,
opening: [],
movesPlayed: [],
remainingGuesses: 5,
guessIDs: ['guess1']
}
},
methods: {
setMode(value) {
this.lengthGuess = value
const self = this
const fetch_url = document.querySelector('#app').getAttribute(
'fetch-url')
return fetch(fetch_url + '?move_count=' + value)
.then((response) => response.json())
.then((result) => {
self.opening = result.moves
})
},
storeMove(move) {
this.movesPlayed.push(move)
},
undoMove() {
this.movesPlayed.pop()
},
clearMoves() {
this.movesPlayed = []
},
checkGuess() {
if (this.movesPlayed.length < this.lengthGuess * 2) {
alert('You must play more moves.')
return
}
for (let i = 0; i < this.lengthGuess * 2; i++) {
const move = this.movesPlayed[i]
const answer = this.opening[i]
const guessNum = 6 - this.remainingGuesses
const moveCard = document.querySelectorAll(
`#guess${guessNum} .card`)[i]
if (move == answer) {
moveCard.classList.remove('bg-dark')
moveCard.classList.add('bg-success')
}
else if (this.opening.find((_str) => _str == move)) {
moveCard.classList.remove('bg-dark')
moveCard.classList.add('bg-warning')
}
}
this.remainingGuesses--
this.guessIDs.push(`guess${6 - this.remainingGuesses}`)
},
},
components: { MoveGroup, ModalInstructions, ChessBoard }
Where am I going wrong here ?
Thanks !
Edit:
Here's the code for the MoveGroup component:
Components/move-group.vue
<template>
<div class="d-flex col">
<span class="mx-1" style="font-size:1.5rem; font-weight:bold;">
<slot/>.
</span>
<div>
<div class="card bg-dark" style="width:4rem; height:4rem;">
<div class="card-body row">
<p class="card-text align-middle align-self-center text-center text-light">
</p>
</div>
</div>
</div>
<div class="col-1"></div>
<div>
<div class="card bg-dark" style="width:4rem; height:4rem;">
<div class="card-body row">
<p class="card-text align-middle align-self-center text-center text-light">
</p>
</div>
</div>
</div>
</div>
</template>
Upvotes: 0
Views: 573
Reputation: 312
Direct DOM manipulation is a trap!!! in Vue. That's why many javascript libraries needs an adaptation to SPA frameworks.
You should refactor the CSS changes to bind the component with probably props. Here's is an example, but the logic is not right. So you need to adapt as your needs.
So, avoid DOM manipulation and let Vue take care of it, for more info about why this happens: here
const MoveGroup = {
name: "MoveGroup",
template: document.querySelector("#move-group").innerHTML,
props: ["active"]
};
const App = {
name: "App",
template: document.querySelector("#app-template").innerHTML,
data() {
return {
lengthGuess: 2,
opening: ["e5", "c2"],
movesPlayed: ["e5", "c2"],
remainingGuesses: 5,
guessIDs: ["guess1"],
};
},
components: {
MoveGroup,
},
methods: {
addToGuessIds() {
this.remainingGuesses--;
this.guessIDs = [...this.guessIDs, `guess${6 - this.remainingGuesses}`];
},
}
};
Vue.createApp(App).mount('#app');
<template id="move-group">
<div class="d-flex col">
<span class="mx-1" style="font-size: 1.5rem; font-weight: bold">
<slot></slot>
</span>
<div>
<div
:class="['card', active ? 'bg-success' : 'bg-dark']"
style="width: 4rem; height: 4rem"
>
<div class="card-body row">
<p
class="card-text align-middle align-self-center text-center text-light"
></p>
</div>
</div>
</div>
<div class="col-1"></div>
<div>
<div
:class="['card', active ? 'bg-success' : 'bg-dark']"
style="width: 4rem; height: 4rem"
>
<div class="card-body row">
<p
class="card-text align-middle align-self-center text-center text-light"
></p>
</div>
</div>
</div>
</div>
</template>
<template id="app-template">
<div>
<ul id="guessList" class="list-unstyled">
<li v-for="(guess, index) in guessIDs" :key="guess">
<div
:id="guess"
class="row justify-content-center align-self-center mb-3"
>
<move-group
v-for="n in lengthGuess"
:key="`${guess}_move_group${n}`"
:active="index !== guessIDs.length - 1"
>{{ n }}
</move-group>
</div>
</li>
</ul>
<button @click="addToGuessIds">Add</button>
</div>
</template>
<div id="app"></div>
<script src="https://unpkg.com/vue@3"></script>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-gH2yIJqKdNHPEq0n4Mqa/HGKIhSkIHeL5AyhkYV8i59U5AR6csBvApHHNl/vI1Bx" crossorigin="anonymous">
Upvotes: 1