Functional React component freezes webpage - reactjs
I'm currently working on a clone of a memory game called Indefinite Interrogation using React (GitHub here). I'm trying to improve the algorithm for asking questions, but my recent changes to it are for some reason causing my component to freeze the webpage.
I think everything going wrong is due to some bad useState() calls but I'm not sure how to fix it.
Here's the file causing problems (ignore the massive amount of code before the function Game() component; I'm only including everything before that for anyone trying to reproduce my problem since the code below differs from the GitHub version):
import React, { useState, useEffect } from 'react'
import buttonPress from '../sounds/button_press.mp3'
import buttonWrong from '../sounds/wrong_answer.mp3'
function randomArrayElement(array) {
const randIndex = Math.floor(Math.random() * array.length);
return array[randIndex];
}
// Returns an array of length n, with no duplicates, from
// random elements in array
function getNRandomAnswers(array, n, correctAnswer = null) {
let answerSet = new Set();
while (answerSet.size < n) {
const randomAnswer = randomArrayElement(array);
if (randomAnswer !== correctAnswer) {
answerSet.add(randomAnswer);
}
}
return Array.from(answerSet);
}
// Durstenfeld shuffle; see https://stackoverflow.com/questions/2450954/how-to-randomize-shuffle-a-javascript-array
function shuffleArray(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
}
class Question {
constructor(questionText, possibleAnswers) {
this.questionText = questionText;
this.possibleAnswers = possibleAnswers;
this.hasCorrectAnswer = false;
this.correctAnswer = null;
// possibleAnswers is all the possible answer choices that can be used to
// build displayedAnswers. displayedAnswers is exactly 4 answers, with only
// 1 being the correct one
this.createDisplayedAnswers();
}
createDisplayedAnswers() {
if (this.correctAnswer !== null) {
const wrongAnswers = getNRandomAnswers(this.possibleAnswers,
3, this.correctAnswer);
this.displayedAnswers = [this.correctAnswer, ...wrongAnswers];
shuffleArray(this.displayedAnswers);
}
else {
const randomAnswers = getNRandomAnswers(this.possibleAnswers, 4);
this.displayedAnswers = [...randomAnswers];
// No shuffling needed as the answers are chosen randomly; we only
// need to shuffle to make sure the correct answer isn't always displayed
// in the same place
}
}
setCorrectAnswer(answer) {
this.correctAnswer = answer;
this.hasCorrectAnswer = true;
}
reset() {
this.correctAnswer = null;
this.hasCorrectAnswer = false;
this.createDisplayedAnswers();
}
}
const possibleNames = ['Danny James', 'Megan Jenkins',
'Alexander Patel', 'Kwabena Ihejirika', 'Mei Ueda', 'Nishant Chowdhury',
'Yejide Afolayan', 'Tina Powell', 'Hong Wu', 'Kevin Campbell', 'Ai Chen',
'Seok Yi', 'Robert Dixon', 'Jack Marshall'];
const possibleLocations = ['China', 'Canada',
'The Philippines', 'India', 'Indonesia', 'Mexico', 'Russia', 'Brazil',
'The United States', 'The United Kingdom', 'Japan', 'Italy', 'Germany'];
const possibleAges = ['51', '35', '33', '32', '37', '19', '43', '31', '38',
'24', '45', '46', '16', '49', '27', '42', '30', '52', '21'];
const possibleActivities = ['Visiting a friend', 'Drinking', 'Skydiving', 'Working',
'Playing video games', 'Getting married', 'Hiking', 'Selling drugs', 'Cooking drugs',
'Enjoying nature', 'Using the bathroom', 'Ice skating'];
const possibleNumKilled = ['0', '1', '2', '3', '4', '5-10'];
const possibleRelations = ['My grandfather', 'My brother',
'My uncle', 'My mother', 'My sister', 'My grandmother', 'My father', 'My aunt']
const possibleOccupations = ['Exobiologist', 'Computer Hardware Engineer',
'Nuclear Engineer', 'Linguist', 'Software Engineer', 'Actor', 'Chemist',
'Aerospace Engineer', 'Floral Designer', 'Architect', 'Writer', 'Civil Engineer'];
const possibleReasons = ['For political reasons', 'For a loved one',
'Just for fun', 'For a friend', 'For protection', 'Out of necessity',
'For the children'];
const possibleBuddhismReasons = [...possibleReasons, "I don't know"];
const possibleValuablesLocations = ['Under the kitchen sink', 'Behind my bookshelf',
'Under the floorboards', 'In the basement', 'The bathroom cabinet', 'Under my bed',
'My bedroom closet'];
const possibleComputerTimes = ['10:45 AM', '1:45 PM', '7:30 AM', '4:00 PM', 'Midnight',
'8:00 AM', '6:00 PM', '9:06 AM', '5:40 PM', 'Noon'];
const possibleDrugReasons = [...possibleReasons, 'It clears a guilty heart'];
const possibleReligions = ['Christianity', 'None', 'Hinduism', 'Judaism', 'Islam'];
const possibleSisterLocations = [...possibleLocations, "I don't know"];
const possibleSisterLunchTimes = ['1PM - 2PM', '10PM - 4AM', '6AM - 9PM', '5AM - 12AM',
'2PM - 3PM', '2AM - 3AM', '3AM - 4AM', '1AM - 2AM', '7AM - 3PM', '8AM - 6PM', '11AM - 12PM'];
const possibleSisterLastSeenTimes = ['2 days ago', '10 years ago', 'Before The Incident',
'Last month', '8 years ago', '9 years ago', 'Last year', 'Last week', '11 years ago', '7 years ago',
'6 years ago', 'Yesterday', 'A few hours ago'];
const possibleTalkTimes = ['Never', 'Tomorrow', 'In a few days', 'In a few weeks',
'Next week', 'In about a year', 'In several months'];
const possibleMemorySounds = ["I don't remember", 'There are four facets of mind',
'We brought you here for a reason', 'When did you exist?'];
const possibleDestinations = ['Neptune', 'The Asteroid Belt', 'Jupiter', 'Saturn',
'The Moon colony', 'Mars'];
const possibleWeapons = ['Clubs', 'Submachine guns', 'Pistols', 'Antimatter mints',
'Swords', 'Javelins'];
const possibleRestaurants = ['The White Rug', 'Trounce Steaks', 'The Dancing Crow',
'The Eating Hole', 'The Blue Daisy', "Supreme Overlord's Noodles", 'Stellar Seafood'];
const possibleStrange = ['A hooded figure', 'A glowing skyward object',
'A one-eyed man', 'A white motor vehicle'];
const possibleTerroristLeaders = ['Lady Dukkha', 'Dr. Smith', "I'm not a terrorist",
"I don't know", '...What?'];
const possibleDepartureHelpers = [...possibleRelations, 'No one!'];
const possibleDrugSellers = [...possibleRelations, 'A stranger'];
const possibleCriticismReasons = ['She was angry', 'That question is unfair',
"I don't remember", 'She felt it was important', 'For school'];
const possibleCodes = ['Sbyybj #OenaqYvory', 'Va gur qlfgbcvn2a shgher',
'Guvf zft vf abg 1frpher', 'Vaqrsvavgr frdhry'];
const list1 = [
new Question('What is your full name?', possibleNames),
new Question('How old are you?', possibleAges),
new Question('Where were you born?', possibleLocations),
new Question('Where were you during The Incident?', possibleLocations),
new Question('What were you doing the day of The Incident?', possibleActivities),
new Question('How many did you kill during The Incident?', possibleNumKilled),
new Question('Who are you closest to?', possibleRelations),
new Question('What is your occupation?', possibleOccupations),
new Question('Where in your home do you keep your valuables?', possibleValuablesLocations),
new Question('Which religion do you identify with?', possibleReligions)
]
const list2 = [
new Question('How old is your sister?', possibleAges),
new Question('Your sister converted to Buddhism. Why?', possibleBuddhismReasons),
new Question("What was your sister's occupation?", possibleOccupations),
new Question('What are the whereabouts of your sister?', possibleSisterLocations),
new Question('When does your sister usually go out for lunch?', possibleSisterLunchTimes),
new Question('When did you last see your sister in person?', possibleSisterLastSeenTimes),
new Question('When did your sister run away from home?', possibleSisterLastSeenTimes),
new Question('When were you planning to talk to us about your sister?', possibleTalkTimes),
new Question('Why have you been buying illegal drugs like SpyteFire?', possibleDrugReasons),
new Question('Who sold you illegal drugs like SpyteFire?', possibleDrugSellers),
new Question('When did you last log into a computer console?', possibleComputerTimes),
new Question('When do you usally go out for lunch?', possibleSisterLunchTimes)
]
const list3 = [
new Question("Who is your sister's significant other?", possibleNames),
new Question('Years ago, your sister criticized the government. Why?', possibleCriticismReasons),
new Question('What did you hear, just as you lost your memory?', possibleMemorySounds),
new Question('Where were you planning to go after leaving Earth?', possibleDestinations),
new Question('What kinds of weapons have you been buying?', possibleWeapons),
new Question('In which restaurant do you have your lunch meetings?', possibleRestaurants),
new Question('What period of The Incident do you have no memory of?', possibleSisterLunchTimes),
new Question('What seemed strange to you the day before The Incident?', possibleStrange),
new Question('Who is the leader of your terrorist cell?', possibleTerroristLeaders),
new Question('Before we got you, when were you planning to leave Earth?', possibleTalkTimes),
new Question('Who helped arrange your departure from Earth?', possibleDepartureHelpers),
new Question('Why were you planning to leave Earth?', possibleReasons),
new Question('You recently received a coded message. What did it say?', possibleCodes),
new Question('When did you search "overthrow government" yesterday?', possibleComputerTimes)
]
const questions = [
list1,
list2,
list3
]
const wrongAnswerReplies = ['You slipped up!', 'Liar! You contradicted yourself.',
"Your responses don't match.", 'Liar!'
]
const correctAnswerVariations = ["Here's your previous answer: ", 'This was what you said before: ',
'Earlier you said: '
]
const questionIntroVariations = ['I see.', 'Interesting.']
function getWrongAnswerReply() {
return randomArrayElement(wrongAnswerReplies) + ' ' + randomArrayElement(correctAnswerVariations);
}
function getInitialQuestionList(questionsIndices) {
let initialList = [list1[0], list1[1], list1[2]]
while (initialList.length < 10) {
const nextIndex = randomArrayElement(questionsIndices)
const nextQuestion = randomArrayElement(questions[nextIndex])
const lastInitialListIndex = initialList.length - 1
if (nextQuestion.text !== initialList[lastInitialListIndex].text) {
initialList.push(nextQuestion)
}
}
return initialList
}
function getNextQuestionList(questionsIndices) {
let nextList = []
while (nextList.length < 10) {
const nextIndex = randomArrayElement(questionsIndices)
const nextQuestion = randomArrayElement(questions[nextIndex])
const lastListIndex = nextList.length - 1
if (nextList.length === 0) {
nextList.push(nextQuestion)
}
else if (nextQuestion.text !== nextList[lastListIndex].text) {
nextList.push(nextQuestion)
}
}
}
function Game(props) {
const [questionsIndices, setQuestionsIndices] = useState([0])
const [questionList, setQuestionList] = useState(getInitialQuestionList(questionsIndices))
const [currentListIndex, setCurrentListIndex] = useState(0)
const [question, setQuestion] = useState(questionList[0])
// useEffect(() => {
// setQuestionsIndices([0])
// setQuestionList(qL => getInitialQuestionList(questionsIndices))
// setQuestion(q => questionList[0])
// setCurrentListIndex(0)
// console.log('finished')
// }, [questionList, questionsIndices])
const answers = [...question.displayedAnswers]
function getNextQuestion() {
if (props.score > 0 && props.score % 10 === 0) {
const nextQuestionsIndex = questionsIndices[questionsIndices.length - 1] + 1
if (nextQuestionsIndex < questions.length) {
setQuestionsIndices(qI => [...qI, nextQuestionsIndex])
}
setQuestionList(getNextQuestionList(questionsIndices))
setCurrentListIndex(0)
}
const currentQuestion = questionList[currentListIndex]
setCurrentListIndex(lI => lI + 1)
return currentQuestion
}
function handleClick(answerIndex) {
// Prevents users from continuing to further questions after wrong answers
if (props.isGameOver) { return; }
// console.log('Test: ' + answerIndex);
const chosenAnswer = answers[answerIndex]
if (!question.hasCorrectAnswer) {
const buttonSound = new Audio(buttonPress)
buttonSound.play()
question.setCorrectAnswer(chosenAnswer)
// Make sure the answers aren't displayed the same way next time
question.createDisplayedAnswers()
setQuestion(getNextQuestion())
props.setScore(props.score + 1)
}
else if (chosenAnswer === question.correctAnswer) {
const buttonSound = new Audio(buttonPress)
buttonSound.play()
question.createDisplayedAnswers()
setQuestion(getNextQuestion())
props.setScore(props.score + 1)
}
else {
const wrongAnswerSound = new Audio(buttonWrong)
wrongAnswerSound.play()
props.setGameOver(true);
setTimeout(() => {
// This forEach is required to reset the Question answers;
// otherwise they'll keep the correctAnswer from the previous session!
questions.forEach(q => {
q.reset();
})
setQuestion(questions[0]);
props.setGameOver(false);
props.setScore(0);
// Return to Main Menu
props.setGameStarted(false);
}, 3000)
}
}
function getQuestionText() {
let displayedText = ''
const randomNum = Math.random() * 100
// 0-9 is ten numbers, so this should have a 10% chance of being called
// props.score can't be 0; otherwise this might get called for the very first question!
// That wouldn't match the original game's behavior...
if (randomNum < 10 && props.score !== 0) {
displayedText = randomArrayElement(questionIntroVariations) + ` ${question.questionText}`
}
else {
displayedText = question.questionText
}
return displayedText
}
return (
<>
<p>{props.score}</p>
<p className="game-question">{props.isGameOver ? `${getWrongAnswerReply()} ${question.correctAnswer}`
: getQuestionText()}</p>
<div className="container">
<div className="row row-eq-height">
<div className="col">
<button className="btn button"
onClick={() => handleClick(0)}>{answers[0]}</button>
</div>
<div className="col">
<button className="btn button"
onClick={() => handleClick(1)}>{answers[1]}</button>
</div>
</div>
<div className="row row-eq-height">
<div className="col">
<button className="btn button"
onClick={() => handleClick(2)}>{answers[2]}</button>
</div>
<div className="col">
<button className="btn button"
onClick={() => handleClick(3)}>{answers[3]}</button>
</div>
</div>
</div>
</>
);
}
export default Game;
I tried useEffect to solve my problem but that didn't work. Sorry for any bad coding/styling practices; I'm new to functional React components and I have a bad habit of using semi-colons even though I don't actually have to.
(Adding this as a formal answer since it solved the OP's issue).
The bug is in the getInitialQuestionList() function, which currently contains an infinite loop - the length of the array never increases. As you yourself identified, you were using the wrong .text property on the question instead of the correct .questionText.
How I found out the problem
I copied and pasted your code into a new React CodeSandbox, which showed me a nice error message saying there was a likely infinite loop and the exact line in the function. I just didn't dig into it long enough after that to figure out why the infinite loop was happening, though.
P.S. If you were using TypeScript here, the TS Compiler likely would have caught this error and saved you a lot of bother. Just food for thought.
Related
Array: How to change specific values dependent on index (Rating function)
I'm sorry for the terrible title, but somehow I can't explain it better in one sentence. What I want to do is a rating component in my Vue App. So if I click the 3rd star, the two stars before that one are set to "true" as well. What I got: const ratingsArray = [ { name: 'rating1', ratingCount: 1, isClicked: ref(false) }, { name: 'rating2', ratingCount: 2, isClicked: ref(false) }, { name: 'rating3', ratingCount: 3, isClicked: ref(false) }, { name: 'rating4', ratingCount: 4, isClicked: ref(false) }, { name: 'rating5', ratingCount: 5, isClicked: ref(false) }, ] I just got a toggle function to toggle isClicked: function toggleClick(x) { x.value = !x.value } This is my template <template> <div v-for="rating in ratingsArray" :key="rating.name" #click="toggleClick(rating.isClicked)" :class="[rating.isClicked.value ? 'ratingBoxFilled' : 'ratingBox']"> </div> </template> How can I say, that if rating3 is clicked (so isClicked is true), rating1 and rating2 also got to be true? It seems that I need to work with the index in my array. But somehow, I cannot create an idea. Maybe you guys can help me out. Thank you!
A simple loop would do the trick: <template> <div v-for="(rating, index) in ratingsArray" :key="rating.name" #click="toggleClick(index)" :class="[rating.isClicked.value ? 'ratingBoxFilled' : 'ratingBox']"> </div> </template> function toggleClick(ratingIndex) { for (let i = 0; i < ratingsArray.length; i++) { // Set ratingsArray[i].isClicked to true if it's within the requested range ratingsArray[i].isClicked.value = (i <= ratingIndex); } }
You guys are great. I didn't thought of an for loop. So here is my final solution: <div v-for="(rating, index) in ratingsArray" :key="rating.name" #click="updateRating(index, rating.ratingValue)" :class="[rating.isEnabled.value ? 'ratingBoxChecked' : 'ratingBox']"> </div> function updateRating(ratingIndex: number, ratingValue: number) { for (let i = ratingIndex; i < 5; i++) { ratingsArray[i].isEnabled.value = false; } for (let i = 0; i <= ratingIndex; i++) { ratingsArray[i].isEnabled.value = true; } console.log('Rating Value: ' + ratingValue) } First I clean all the enabled dots. Then it will run until the given index and set the boolean value to true. Thats all.
Filter an array by category and keyword
So I'm trying to filter an array by the category of an item and by its title. For example, I have a few jobs, each job is an object, containing a job title and an array of categort, so: const category = [ 'Web designer', 'App developer', 'Cleaning', 'Designer', ]; const [userSelectedCategory, setUserSelectedCategory] = useState([]); const jobs = [ { title: 'Job 1', category: ['Web designer', 'App developer'], }, { title: 'Job 2', category: ['Cleaning', 'Web Designer'], }, { title: 'Design', category: ['Designer', 'Web Developer'], }, ]; const categoryoptions = category.map(el=>{ return <button onClick={()=>setUserSelectedCategory(prev=>{...prev, el})}>{el}</button> //Now each time a user clicks on a button, we can compare the userSelectedCategory array to the individual job category array. }); So my approach was to do something like this: //This value is set whenever a user searches in search bar const [keyword, setKeyword] = useState(''); const pattern = new RegExp('\\b' + keyword.replace(/[^a-zA-Z0-9 ]/g, ''), 'i'); const filteredjobs = jobs?.filter( (job) => (keyword === '' && category.length === 0) || (category.some((el) => job.category.includes(el)) && pattern.test(job.title)) ); So the HTML would look like this: <div> <input onChange={e=>setKeyword(e.target.value)} /> {categoryoptions} {filteredjobs} </div> But the issue with this is that if no categories are selected, then nothing will get returned as default. So to explain how I want my filter to work, I want all of the jobs to be displayed if the keyword is blank (input is empty). But if the input is not empty, I want to filter by that value. In addition, I want it to filter by category even if the keyword is blank. Lastly, I want it to filter by keyword and by category and both of them are not empty.
use conditional rendering in place of {filteredjobs}: <input value={keyword} onChange={e => setKeyword(e.target.value)} /> {keyword ? filteredjobs : jobs}
FormArray does not print on my Reactive Form
I'm starting at Angular and I'm having a really hard time setting a FormArray inside my form. In summary what I did was: Create 2 forms: the main (valForm) and another one created dynamically (holidaysForm). Copy the values of the second form to an Array. Load the values of the Array into a FormArray. Follow the codes: .ts let group = {} this.holidaysForm = new FormGroup(group); this.valForm = fb.group({ dentistId: [null, Validators.required], initialHour: [null, Validators.required], endHour: [null, Validators.required], numberOfHolidays: [null], appointmentsInterval: [null, Validators.required], year: [null, Validators.required], workDays: this.buildDays(), holidays: this.buildHolidays() }); buildDays() { const values = this.workDays.map(v => new FormControl(false)); return this.fb.array(values); } buildHolidays() { if (typeof this.valForm !== 'undefined') { let teste = Object.assign({}, this.holidaysForm.value); this.holidays = new Array(); Object.values(teste).forEach((v) => { this.holidays.push(v); }) console.log(this.holidays); const values = this.holidays.map((v)=> new FormControl(v)); return this.fb.array(values); } } dynamicForm(val) { if (val > 365) { val = 365; this.valForm.patchValue({ numberOfHolidays: 365 }) } const val_plus_one = parseInt(val) + 1 let i: number = val_plus_one; if (i < this.oldNumber) { do { this.holidaysForm.get('holiday' + i.toString()).setValue(null); this.holidaysForm.removeControl('holiday' + i.toString()); i++ } while (i <= this.oldNumber) } const numbers = Array(val).fill(val, 0, 365).map((_, idx) => idx + 1) numbers.forEach((num) => { const fc = new FormControl('', FormValidations.Calendar(this.valForm.get('year').value)); this.holidaysForm.addControl('holiday' + num.toString(), fc) }) this.numberOfHolidays = numbers; this.oldNumber = val; } .html <div class="col-md-4 mda-form-group"> <input class="mda-form-control" type="number" min="0" max="365" formControlName="numberOfHolidays" (change)="dynamicForm(valForm.get('numberOfHolidays').value)" /> <label>Total de Feriados</label> </div> <div [formGroup]="holidaysForm"> <div class="mda-form-group" *ngFor="let num of numberOfHolidays"> <input class="mda-form-control" type="date" formControlName="holiday{{num}}" /> <label>Feriado {{num}}</label> <app-error-msg [control]="holidaysForm.get('holiday'+num)" label="Feriado"> </app-error-msg> </div> </div> In theory the code works well and without errors, the problem is that the result is always this: Main Form Valores: { "dentistId": null, "initialHour": null, "endHour": null, "numberOfHolidays": 3, "appointmentsInterval": null, "year": null, "workDays": [ false, false, false, false, false, false, false ], "holidays": null } Holiday Form { "holiday1": "2020-01-01", "holiday2": "2020-03-01", "holiday3": "2020-02-01" } Does anyone have any idea what I might be doing wrong? Thankful,
The problem seems to be you are using holidaysForm before you ever put values in it. So at the point where you are creating the object to loop through to create your array, that object will have no properties. buildHolidays() { if (typeof this.valForm !== 'undefined') { let teste = Object.assign({}, this.holidaysForm.value); // this line this.holidays = new Array(); Object.values(teste).forEach((v) => { this.holidays.push(v); }) console.log(this.holidays); const values = this.holidays.map((v)=> new FormControl(v)); return this.fb.array(values); } } I would figure out how to get values to use in this method to make it all work. And remember, you can always add a formArray after the creation of your original formgroup via group.addControl('controlName', myFormArray); Best of luck, and as always Happy Coding
Adding Selected Option Prices for Checkboxes - Using Svelte
I'm trying to get update the total as the checkboxes are selected and unselected (incase the user changes their mind and no longer wants the item). But I'm not sure how to get the actual value I have assigned to each toy. For example: If the user selects toy 1 and 3 they should see: You ordered Toy1, Toy3 and your total is $6.00. For now I assigned the values with the names themselves which isn't what I want but I just put that to show what I'm trying to do. Is their something else I should be using actually perform an operation to get the total? <script> let toylist = []; let toynames = [ 'Toy1 5.00', 'Toy2 5.00', 'Toy3 1.00', ]; function join(toylist) { return toylist.join(', '); } </script> {#each toynames as toy} <label> <input type=checkbox bind:group={toylist} value={toy}> {toy} </label> {/each} {#if toylist.length === 0} <p>Pick at least one toy</p> {:else} <p> You ordered {toylist.join(', ')} and your total is </p> {/if}
Ok, first you should separate the toynames array into an array of names and values. Then you should bind to the checked property of the input. In order to display the current state to the user, we need a reactive declaration. Let's call it total. It contains two functions. The first one gives back an array of the selected names, the second the sum of the selected values. Both work by looking at the selected property of the object in the toylist array. This updates due to our binding of the checked attribute. I created a repl for you to toy ;-) around with <script> let toylist = [ {name: "Toy", value: 5, selected: false}, {name: "Elephant", value: 6, selected: false}, {name: "Choo-Choo", value: 1, selected: false} ] $: total = { names: toylist.reduce((acc, cV) => { if (cV && cV.selected) { return [...acc, cV.name]; } else return [...acc]; }, []), values: toylist.reduce((acc, cV) => { if (cV && cV.selected) { return parseInt(acc) + parseInt(cV.value); } else return parseInt(acc); }, 0) }; </script> {#each toylist as {name,value, selected}} <label> <input type=checkbox bind:checked={selected} bind:value={value}> {name} </label> {/each} {#if toylist.length === 0} <p>Pick at least one toy</p> {:else} <p> You ordered {total.names.length < 1 ? "nothing": total.names} and your total is {total.values} </p> {/if} EDIT: Here is the total function with a more classic syntax: $: total = { names: toylist.reduce(function(acc, cV) { if (cV && cV.selected) { return [...acc, cV.name]; } else return [...acc]; }, []), values: toylist.reduce(function(acc, cV) { if (cV && cV.selected) { return parseInt(acc) + parseInt(cV.value); } else return parseInt(acc); }, 0) }; And here without the ? operator: <script> function renderNames() { if (total.names.length < 1) { return "nothing"; } else { return total.names; } } </script> <p>You ordered {renderNames()} and your total is {total.values}</p>
The best way to isolate the price of each toy would be to make your array of toys into an array of objects where 'name' is one key value pair and price another. For manipulating the data it would be helpful if each toy had an id, and I've added a 'selected' boolean value to each toy that is updated if they are added or removed from the "toylist". I've also added a 'total' variable to hold the total of selected toys prices. I have played with your code a bit to make this work. I have used buttons instead of checkboxes but you could do it in any way you like. So give this code a go and it should be doing what you want. <script> let toylist = []; let toys = [ {id: 1, name: 'Toy 1', price: 5.00, selected: false}, {id: 2, name: 'Toy 2', price: 5.00, selected: false}, {id: 3, name: 'Toy 3', price: 1.00, selected: false} ]; let total = 0.00 function join(toy) { if(toy.selected === false) { toylist = [...toylist, toy.name] total = total+toy.price let i = toys.findIndex(t => t.id === toy.id) toys[i].selected = true toys = toys } else { total = total-toy.price let i = toys.findIndex(t => t.id === toy.id) let i2 = toylist.findIndex(t => t === toy.name) toylist.splice(i2, 1) toylist = toylist toys[i].selected = false toys = toys } } </script> {#each toys as toy} <label> {toy.name}: ${toy.price} <button on:click="{() => join(toy)}" value={toy.name}>{toy.selected ? 'remove' : 'add'}</button> </label> {/each} {#if toylist.length === 0} <p>Pick at least one toy</p> {:else} <p> You ordered {toylist} and your total is ${total} </p> {/if}
Wrapping an an html anchor tag around an array is causing unexpected results in my React component
I have a component that I use in my react app that generates a random game on the screen. It's working, but now I'm trying to add some html into the game title. When I do that, my game titles come up as: [object Object] Here is where I generate a random game: const newGame = () => { return { title: gameTitleArray[Math.floor(Math.random() * gameTitleArray.length)], type: gameTypeArray[Math.floor(Math.random() * gameTypeArray.length)], startDate: getDate(new Date(2019, 0, 1), new Date()), endDate: getDate(new Date(2022, 0, 1), new Date()), } } You can see I'm trying to wrap an html anchor tag around the 'title' portion. Here is how I'm exporting the component: export default function makeGameData(...lens) { const makeGameDataLevel = (depth = 0) => { const len = lens[depth] return range(len).map(d => { return { ...newGame(), subRows: lens[depth + 1] ? makeGameDataLevel(depth + 1) : undefined, } }) } return makeGameDataLevel() } Here is an example of the gameTypeArray: const gameTypeArray = ['RPG', 'Western', 'Real-Time Strategy', 'Fantasy', 'First Person Shooter'] And an example of gameTitleArray: const gameTitleArray = ['Future Agent','Human Universe','Chase of Resitution','Destroy of Resitution','Days and Glitch','Mayhem and Faith','Dynaworks','Crystalback','Fusionheart','Hellscape'] I even tried creating a separate function like this: function gameTitleArrayLink() { const gameTitleArray = ['Future Agent','Human Universe','Chase of Resitution','Destroy of Resitution','Days and Glitch','Mayhem and Faith','Dynaworks','Crystalback','Fusionheart','Hellscape'] const title = gameTitleArray[Math.floor(Math.random() * gameTitleArray.length)] const titleUrl = {title} return <div dangerouslySetInnerHTML={{ __html: titleUrl }} /> } And then setting the title like this: title: researchSummaryList() Obviously I'm doing something wrong, but I'm not getting any errors, just the [object Object] Is there anything I'm doing wrong? Thanks!
Try wrapping the array value in {} to have it treated as an expression: {gameTitleArray[Math.floor(Math.random() * gameTitleArray.length)]}
I think that the item you want to render (gameTitleArray[Math.floor(Math.random() * gameTitleArray.length)]) is an object and not a string