I have created one quiz application.The questions and answers are coming from backend API.On the browser window whenever i am clicking any answer ,API is getting called again.On every click api is getting called.How to resolve this.Can anyone help me with this?
I have tried useeffect,use state .But no success
Here is the code
import React from "react";
import "./Quiz.css";
import { useState, useEffect } from "react";
function Quiz() {
const [cname, scname] = useState([]);
const [results, finalres] = useState(false);
const [score, setScore] = useState(0);
const [currentQues, setCurrentQues] = useState(0);
//Get Method
useEffect(() => {
fetch("https://countriesnow.space/api/v0.1/countries/capital")
.then((response) => {
return response.json();
})
.then((data) => {
scname(data.data);
});
}, []);
const restartGame = () => {
setScore(0);
setCurrentQues(0);
finalres(false);
shuffledAnswers(currentQues);
};
let namearray = [5];
var capitalArray = [5];
let fx = (i) => {
let countryName = "";
let capitalName = "";
let countryNum = Math.floor(Math.random() * cname.length);
if (countryNum) {
countryName = cname[countryNum].name;
capitalName = cname[countryNum].capital;
}
namearray[i] = countryName;
capitalArray[i] = capitalName;
};
for (let i = 0; i < 5; i++) {
fx(i);
}
const shuffledAnswers = (currentQues) => {
let shuffled = questions[currentQues].answerOptions
.map((value) => ({ value, sort: Math.random() }))
.sort((a, b) => a.sort - b.sort)
.map(({ value }) => value);
// console.log(shuffled)
return shuffled;
};
const questions = [
{
questionText: "What is the capital of " + namearray[0] + "?",
answerOptions: [
{ answerText: capitalArray[0], isCorrect: true },
{ answerText: capitalArray[1], isCorrect: false },
{ answerText: capitalArray[2], isCorrect: false },
{ answerText: capitalArray[3], isCorrect: false },
],
},
{
questionText: "What is the capital of " + namearray[1] + "?",
answerOptions: [
{ answerText: capitalArray[4], isCorrect: false },
{ answerText: capitalArray[1], isCorrect: true },
{ answerText: capitalArray[0], isCorrect: false },
{ answerText: capitalArray[3], isCorrect: false },
],
},
{
questionText: "What is the capital of " + namearray[2] + "?",
answerOptions: [
{ answerText: capitalArray[1], isCorrect: false },
{ answerText: capitalArray[0], isCorrect: false },
{ answerText: capitalArray[2], isCorrect: true },
{ answerText: capitalArray[3], isCorrect: false },
],
},
{
questionText: "What is the capital of " + namearray[3] + "?",
answerOptions: [
{ answerText: capitalArray[0], isCorrect: false },
{ answerText: capitalArray[2], isCorrect: false },
{ answerText: capitalArray[1], isCorrect: false },
{ answerText: capitalArray[3], isCorrect: true },
],
},
{
questionText: "What is the capital of " + namearray[4] + "?",
answerOptions: [
{ answerText: capitalArray[4], isCorrect: true },
{ answerText: capitalArray[1], isCorrect: false },
{ answerText: capitalArray[2], isCorrect: false },
{ answerText: capitalArray[3], isCorrect: false },
],
},
];
const hoc = (isCorrect) => {
if (isCorrect === true) {
setScore(score + 1);
}
const nextq = currentQues + 1;
if (nextq < questions.length) {
setCurrentQues(nextq);
} else {
finalres(true);
}
};
console.log('data')
return (
<>
<h4>Quiz</h4>
<div id="head">Welcome User</div>
<hr />
{results ? (
<div className="final-res">
Final Results
<h4>you scored {score} out of 5</h4>
<button onClick={() => restartGame()}>Restart</button>
</div>
) : (
<div>
<div id="quescard">
<h3>
{currentQues + 1}. {questions[currentQues].questionText}
</h3>
{questions[currentQues].answerOptions.map((ansopt) => (
<button onClick={() => hoc(ansopt.isCorrect)}>
{ansopt.answerText}
</button>
))}
</div>
</div>
)}
</>
);
}
export default Quiz
The issue here is you are updating the state of the component whenever any answer option is being clicked by making them buttons.
Instead make them checkboxes and add a additional button below which would allow the user to proceed with his final selection whereby which only allowing to update the state once
Related
I am trying to sort this array by price. If the item is on sale then by its onSalePrice and
if it is not sale then by its price. I have my current solution below but it doesn't work. Any thoughts??
The 'lowest-price' parameter is indicating toward the value in my option field. This parameter is definitely working okay. The issue is in the conditions that I am sorting with
const products = [
{
price: 28.99,
onSalePrice: 22.99,
onSale: "True",
},
{
price: 26.99,
onSalePrice: 22.99,
onSale: "True",
},
{
price: 24.99,
onSale: "False",
},
];
if (sort === "lowest-price") {
if (products.onSale === "True") {
tempProducts = products.slice().sort((a, b) => {
return a.onSalePrice - b.onSalePrice;
});
} else if (products.onSale === "False") {
tempProducts = products.slice().sort((a, b) => {
return a.price - b.price;
});
}
}
const products = [
{
price: 28.99,
onSalePrice: 22.99,
onSale: "True",
},
{
price: 26.99,
onSalePrice: 22.99,
onSale: "True",
},
{
price: 24.99,
onSale: "False",
},
];
const getPrice = o => o.onSale==='True' ? o.onSalePrice : o.price;
let tempProducts = [...products].sort((a,b) => getPrice(a) - getPrice(b));
console.log(tempProducts);
I'm pretty new to React so aware my code is probably a bit of a mess and more complicated than it needs to be.
Currently when a user clicks an answer button "setDisabled" will disable that row of 4 buttons so the user can only select one answer button per question. The state "setButton" changes the button class to "buttonClicked" which changes the background colour to light grey (so the user can see which button they have clicked.
The score increments by 1 for each correct answer and the button "Check Answers" reveals the score.
What I am trying to do is: When the "Check Answers" button is clicked I want the buttons which the user has selected and are correct (they have isCorrect: true) to have their background colour turn green and the buttons which have been clicked by the user but are incorrect (isCorrect: false) to have their background colour turn red.
I have tried to experiment with template literals in the button className and trying some if else logic in the resultsClick function but i feel like I am tangling myself up a little in my head and potentially overcomplicating things.
Any pointers would be much appreciated.
function Quiz(props) {
const questions = [
{
questionText: 'What is the Capital of France?',
answerOptions: [
{ id: 0, answerText: 'London', isCorrect: false, answer: 0 },
{ id: 0, answerText: 'Paris', isCorrect: true, answer: 1 },
{ id: 0, answerText: 'Nice', isCorrect: false, answer: 2 },
{ id: 0, answerText: 'Lyon', isCorrect: false, answer: 3 },
],
},
{
questionText: 'What is the Capital of the UK?',
answerOptions: [
{ id: 1, answerText: 'London', isCorrect: true, answer: 4 },
{ id: 1, answerText: 'New York', isCorrect: false, answer: 5 },
{ id: 1, answerText: 'Sydney', isCorrect: false, answer: 6 },
{ id: 1, answerText: 'Bath', isCorrect: false, answer: 7 },
],
},
{
questionText: 'Who turns out to be the true victor in the Battle of Armageddon in Mortal Kombat?',
answerOptions: [
{ id: 2, answerText: 'Liu Kang', isCorrect: false, answer: 8 },
{ id: 2, answerText: 'Shang Tsung', isCorrect: false, answer: 9 },
{ id: 2, answerText: 'Raiden', isCorrect: false, answer: 10 },
{ id: 2, answerText: 'Shao Kahn', isCorrect: true, answer: 11 },
],
},
{
questionText: 'Under what pseudonym did Stephen King publish five novels between 1977 and 1984?',
answerOptions: [
{ id: 3, answerText: 'Richard Bachman', isCorrect: true, answer: 12 },
{ id: 3, answerText: 'J. D. Robb', isCorrect: false, answer: 13 },
{ id: 3, answerText: 'Mark Twain', isCorrect: false, answer: 14 },
{ id: 3, answerText: 'Lewis Carroll', isCorrect: false, answer: 15 },
],
},
];
const [startQuiz, setStartQuiz] = useState(true);
function startClick() {
setStartQuiz(current => !current);
}
const [score, setScore] = useState(0);
const optionClicked = (optionId, isCorrect, answer) => {
const newDisabledArray = [...disabled, optionId];
setDisabled(newDisabledArray)
const newClickedArray = [...button, answer];
setButton(newClickedArray);
if (isCorrect) {
setScore(score + 1);
}
}
const [button, setButton] = useState([]);
const [disabled, setDisabled] = useState([]);
const [showResults, setShowResults] = useState(true);
function resultsClick() {
setShowResults(current => !current);
}
function restartQuiz() {
setStartQuiz(current => !current);
setShowResults(current => true);
setScore(0);
setDisabled(current => []);
setButton(current => []);
}
return(
<div>
{startQuiz ?
<div className="start-screen">
<div className="start-text">
<h1>Quizzical</h1>
<p>Test your knowledge!</p>
<button onClick={startClick}>Start Quiz</button>
</div>
</div>
:
<div className="quiz">
<div className="firstQuestion">
<h2>{questions[0].questionText}</h2>
</div>
<div className="firstAnswers">
{questions[0].answerOptions.map((answerOption) => {
const {id, isCorrect, answer, answerOptions} = answerOption
return (
<button
className={button.includes(answer) ? "buttonClicked" : "button"}
key={id}
disabled={disabled.includes(id)}
onClick={() => {
optionClicked(id, isCorrect, answer);
}}
>
{answerOption.answerText}
</button>
);
}
)}
</div>
<div className="secondQuestion">
<h2>{questions[1].questionText}</h2>
</div>
<div className="secondAnswers">
{questions[1].answerOptions.map((answerOption) => {
const {id, isCorrect, answer, answerOptions} = answerOption
return (
<button
className={button.includes(answer) ? "buttonClicked" : "button"}
key={id}
disabled={disabled.includes(id)}
onClick={() => {
optionClicked(id, isCorrect, answer);
}}>
{answerOption.answerText}
</button>
);
}
)}
</div>
<div className="thirdQuestion">
<h2>{questions[2].questionText}</h2>
</div>
<div className="thirdAnswers">
{questions[2].answerOptions.map((answerOption) => {
const {id, isCorrect, answer, answerOptions} = answerOption
return (
<button
className={button.includes(answer) ? "buttonClicked" : "button"}
key={id}
disabled={disabled.includes(id)}
onClick={() => {
optionClicked(id, isCorrect, answer);
}}>
{answerOption.answerText}
</button>
);
}
)}
</div>
<div className="fourthQuestion">
<h2>{questions[3].questionText}</h2>
</div>
<div className="fourthAnswers">
{questions[3].answerOptions.map((answerOption) => {
const {id, isCorrect, answer, answerOptions} = answerOption
return (
<button
className={button.includes(answer) ? "buttonClicked" : "button"}
key={id}
disabled={disabled.includes(id)}
onClick={() => {
optionClicked(id, isCorrect, answer);
}}>
{answerOption.answerText}
</button>
);
}
)}
</div>
<br></br>
<div className="resultsSection">
<button
className="button"
onClick={()=> resultsClick()}>
Check Answers!
</button>
<h3 className="resultsText">{showResults ? <p>Score:</p> : <p>Score: {score}/4</p>}</h3>
<button className="button" onClick={restartQuiz}>Restart Quiz</button>
</div>
</div>
}
</div>
)
}```
You can set a style by checking isCorrect property withing map (at least I consider your're using). Consider this:
answerOptions?.map((answer) => (
<div style={{backgroundColor: answer.isCorrect ? 'Green' : 'Red'}}>{answer.answerText}</div>
))
I am trying to build a multiple choice quiz app using React. I am using state to increment the user's score when the correct answer button is clicked however I want the user to be able to 'click' the correct button once as a selection. I have found if the user clicks the correct answer button multiple times each click increases their score.
I have tried to apply a disable key to each of the buttons and use state to change the state to true once the button is clicked however unfortunately this affects every button.
Is there a way I can incorporate the ID in the question Array?
I want each individual answer button to be disabled once clicked.
My code so far:
const questions = [
{
questionText: 'What is the Capital of France?',
answerOptions: [
{ id: 0, answerText: 'London', isCorrect: false },
{ id: 1, answerText: 'Paris', isCorrect: true },
{ id: 2, answerText: 'Nice', isCorrect: false },
{ id: 3, answerText: 'Lyon', isCorrect: false },
],
},
{
questionText: 'What is the Capital of the UK?',
answerOptions: [
{ id: 0, answerText: 'London', isCorrect: true },
{ id: 1, answerText: 'New York', isCorrect: false },
{ id: 2, answerText: 'Sydney', isCorrect: false },
{ id: 3, answerText: 'Bath', isCorrect: false },
],
},
{
questionText: 'Who turns out to be the true victor in the Battle of Armageddon in Mortal Kombat?',
answerOptions: [
{ id: 0, answerText: 'Liu Kang', isCorrect: false },
{ id: 1, answerText: 'Shang Tsung', isCorrect: false },
{ id: 2, answerText: 'Raiden', isCorrect: false },
{ id: 3, answerText: 'Shao Kahn', isCorrect: true },
],
},
{
questionText: 'Under what pseudonym did Stephen King publish five novels between 1977 and 1984?',
answerOptions: [
{ id: 0, answerText: 'Richard Bachman', isCorrect: true },
{ id: 1, answerText: 'J. D. Robb', isCorrect: false },
{ id: 2, answerText: 'Mark Twain', isCorrect: false },
{ id: 3, answerText: 'Lewis Carroll', isCorrect: false },
],
},
];
const [startQuiz, setStartQuiz] = useState(true);
function startClick() {
setStartQuiz(current => !current);
}
const [score, setScore] = useState(0);
const optionClicked = (isCorrect) => {
if (isCorrect) {
setScore(score + 1);
}
}
const [disabled, setDisabled] = useState(false);
const [showResults, setShowResults] = useState(true);
function resultsClick() {
setShowResults(current => !current);
}
function restartQuiz() {
setStartQuiz(current => !current);
setShowResults(current => true);
setScore(0);
setDisabled(false)
}
return(
<div>
{startQuiz ?
<div className="start-screen">
<div className="start-text">
<h1>Quizzical</h1>
<p>Test your knowledge!</p>
<button onClick={startClick}>Start Quiz</button>
</div>
</div>
:
<div className="quiz">
<div className="firstQuestion">
<h2>{questions[0].questionText}</h2>
</div>
<div className="firstAnswers">
{questions[0].answerOptions.map((answerOption) => {
return (
<button key={answerOption.id}
disabled={disabled}
onClick={() => {
optionClicked(answerOption.isCorrect);
setDisabled(true);
}}>
{answerOption.answerText}
</button>
);
}
)}
</div>
<div className="secondQuestion">
<h2>{questions[1].questionText}</h2>
</div>
<div className="secondAnswers">
{questions[1].answerOptions.map((answerOption) => {
return (
<button key={answerOption.id}
disabled={disabled}
onClick={() => {
optionClicked(answerOption.isCorrect)
setDisabled(true);
}}>
{answerOption.answerText}
</button>
);
}
)}
</div>
<div className="thirdQuestion">
<h2>{questions[2].questionText}</h2>
</div>
<div className="thirdAnswers">
{questions[2].answerOptions.map((answerOption) => {
return (
<button key={answerOption.id}
disabled={disabled}
onClick={() => {
optionClicked(answerOption.isCorrect)
setDisabled(true)
}}>
{answerOption.answerText}
</button>
);
}
)}
</div>
<div className="fourthQuestion">
<h2>{questions[3].questionText}</h2>
</div>
<div className="fourthAnswers">
{questions[3].answerOptions.map((answerOption) => {
return (
<button key={answerOption.id}
disabled={disabled}
onClick={() => {
optionClicked(answerOption.isCorrect)
setDisabled(true)
}}>
{answerOption.answerText}
</button>
);
}
)}
</div>
<br></br>
<div className="resultsSection">
<button onClick={()=> resultsClick()}>
Check Answers!
</button>
<h3 className="resultsText">{showResults ? <p>Score:</p> : <p>Score: {score}/4</p>}</h3>
<button onClick={restartQuiz}>Restart Quiz</button>
</div>
</div>
}
</div>
)
}```
First and foremost, it would be great if you could create a reusable component for each set of question+answers to avoid writing too much repeated code e.g.
function Question({questionText, answerOptions}) {
return <>
<div className="question">
<h2>{questionText}</h2>
</div>
<div className="answers">
{answerOptions.map((answerOption) => {
return (
<button key={answerOption.id}
disabled={disabled}
onClick={() => {
optionClicked(answerOption.isCorrect)
setDisabled(true)
}}>
{answerOption.answerText}
</button>
)
}
)}
</div>
</>
}
Next, you could use the useState hook to store an array of numbers indicating which answer option has been clicked:
const [disabledOptions, setDisabledOptions] = useState([])
Edit the optionClicked function such that it receives both the option id and the isCorrect boolean, and add the option id to the disabledOptions array
const optionClicked = (optionId, isCorrect) => {
const newDisabledArray = [...disabledOptions, optionId];
setDisabledOptions(newDisabledArray);
if (isCorrect){
setScore(score + 1);
}
}
Final code will look something like this:
function Question({questionText, answerOptions}) {
const [disabledOptions, setDisabledOptions] = useState([])
const optionClicked = (optionId, isCorrect) => {
const newDisabledArray = [...disabledOptions, optionId];
setDisabledOptions(newDisabledArray);
if (isCorrect){
setScore(score + 1);
}
}
return <>
<div className="question">
<h2>{questionText}</h2>
</div>
<div className="answers">
{answerOptions.map((answerOption) => {
const {id, isCorrect, answerText} = answerOption;
return (
<button key={id}
disabled={disabledOptions.includes(id)}
onClick={() => {
optionClicked(id, isCorrect)
}}>
{answerText}
</button>
)
}
)}
</div>
</>
}
I have created some quizzes. Each box as a total of 5 questions and a complete button. I want to have a progress bar which increases when each section is complete (1-5). Also, I want to have each section greyed out so you can unlock onto the next section. This is access when a using is logged in, I have using PHP and MySQL. I wondered if anyone could suggest a way of achieving this.
HTML
<div class="grid-container">
<div class="grid-item item1">1. Discover HTML Basics and Tags</div>
<div class="grid-item item2">2. Styling HTML with Tags</div>
<div>3. Creating Links and Images</div>
<div class="grid-item item4">4. Building Features</div>
<div class="grid-item item5">5. Building Lists</div>
</div>
JS
$(document).ready(function() {
const startButton = document.getElementById('start-btn')
const nextButton = document.getElementById('next-btn')
const completeButton = document.getElementById('complete-btn')
const questionContainerElement = document.getElementById('question-container')
const questionElement = document.getElementById('question')
const answerButtonsElement = document.getElementById('answer-buttons')
let shuffledQuestions, currentQuestionIndex
startButton.addEventListener('click', startGame)
nextButton.addEventListener('click', () => {
currentQuestionIndex++
setNextQuestion()
})
function startGame() {
startButton.classList.add('hide')
shuffledQuestions = questions.sort(() => Math.random() - .5)
currentQuestionIndex = 0
questionContainerElement.classList.remove('hide')
setNextQuestion()
}
function setNextQuestion() {
resetState()
showQuestion(shuffledQuestions[currentQuestionIndex])
}
function showQuestion(question) {
questionElement.innerText = question.question
question.answers.forEach(answer => {
const button = document.createElement('button')
button.innerText = answer.text
button.classList.add('btn')
if (answer.correct) {
button.dataset.correct = answer.correct
}
button.addEventListener('click', selectAnswer)
answerButtonsElement.appendChild(button)
})
}
function resetState() {
clearStatusClass(document.body)
nextButton.classList.add('hide')
while (answerButtonsElement.firstChild) {
answerButtonsElement.removeChild(answerButtonsElement.firstChild)
}
}
function selectAnswer(e) {
const selectedButton = e.target
const correct = selectedButton.dataset.correct
setStatusClass(document.body, correct)
Array.from(answerButtonsElement.children).forEach(button => {
setStatusClass(button, button.dataset.correct)
})
if (shuffledQuestions.length > currentQuestionIndex + 1) {
nextButton.classList.remove('hide')
} else {
completeButton.innerText = 'Complete'
completeButton.classList.remove('hide')
}
}
function setStatusClass(element, correct) {
clearStatusClass(element)
if (correct) {
element.classList.add('correct')
} else {
element.classList.add('wrong')
}
}
function clearStatusClass(element) {
element.classList.remove('correct')
element.classList.remove('wrong')
}
function completeprogress {
if ()
}
const questions = [
{
question: 'What does HTML stand for?',
answers: [
{ text: 'Hyperlinks and Text Markup Language', correct: true },
{ text: 'Hyper Text Markup Language', correct: false },
{ text: 'Home Tool Markup Language', correct: false }
]
},
{
question: 'Which character is used to indicate an end tag?',
answers: [
{ text: '<', correct: false },
{ text: '*', correct: false },
{ text: '/', correct: true },
{ text: ';', correct: false }
]
},
{
question: 'Who is making the Web standards?',
answers: [
{ text: 'Google', correct: false },
{ text: 'Mozilla', correct: false },
{ text: 'Microsoft', correct: false },
{ text: 'The World Wide Web Consortium', correct: true }
]
},
{
question: 'What is the correct HTML for making a text input field?',
answers: [
{ text: '<input type="textfield">', correct: false },
{ text: '<input type="text">', correct: true },
{ text: '<textfield>', correct: false },
{ text: '<textinput type="text">', correct: false }
]
},
{
question: 'Choose the correct HTML element for the largest heading:',
answers: [
{ text: '<head>', correct: false },
{ text: '<h6>', correct: false },
{ text: '<heading>', correct: false },
{ text: '<h1>', correct: true }
]
}
]
});
The following logic will work. Just got to work out if you want to do it client side or in php.
<?php
// once a quiz is complete, add a status to the session. This could also be stored client side
session_start();
if(!isset($_SESSION['NumberComplete'])){
$_SESSION['NumberComplete'] = 0;
}
// run this when the quiz is completed to increment the counter
$_SESSION['NumberComplete'] = (isset($_SESSION['NumberComplete']) ? $_SESSION['NumberComplete']++ : 1);
// read out the data on page load into a js var
echo '<script>var numberComplete='.$_SESSION['NumberComplete'].';</script>';
?>
<div id="NumberComplete" style="width:100%;height:50px">
<div id="percent" style="background-color: green;width:10%;height: 50px;"></div>
</div>
<script src="https://code.jquery.com/jquery-3.5.0.min.js"></script>
<script>
var totalQuizes = 5;
// run this either on page load, or dynamically when you have a new percentage to update
$(function(){
$("#NumberComplete #percent").css( {"width": (numberComplete / totalQuizes) * 100 + "%" } );
});
</script>
How to update a particular object in array of objects. For example,
have an object like this
tabs: [{
name: "Step 1",
DroppedDetails: [
{id:1,name:Step1,
DroppedDetails:[
{DraggedItemRandomId: "70d19a9f-7e6e-4eb2-b974-03e3a8f03f08"
draggedItem:
category: "basic"
disabled: false
fieldClass: "Text"
height: 30
id: "text"
image: "/static/media/type.327c33c2.png"
label: "Text Field"
placeholder: "Edit This Field"
readOnly: false
required: false
width: 200
}
{DraggedItemRandomId: "70d19a9f-7e6e-4eb2-b974-039e3a8f03f0"
draggedItem:
category: "basic"
disabled: false
fieldClass: "Text"
height: 30
id: "text"
image: "/static/media/type.327c33c2.png"
label: "Text Field"
placeholder: "Edit This Field"
readOnly: false
required: false
width: 200
}
]
},
{
name: "Step 2",
DroppedDetails: [
{DraggedItemRandomId: "70d19a39-7e6e-4eb2-b974-03e3a82f03f0"
draggedItem:
category: "basic"
disabled: false
fieldClass: "Text"
height: 30
id: "text"
image: "/static/media/type.327c33c2.png"
label: "Text Field"
placeholder: "Edit This Field"
readOnly: false
required: false
width: 200
}]
}
],
and my new value should update is
{
DraggedItemRandomId: "70d19a9f-739e-4eb2-b974-03e3a8f032d1",
draggedItem:{
category: "basic"
disabled: false
fieldClass: "Text"
height: 30
id: "text"
image: "/static/media/type.327c33c2.png"
label: "Text Field"
placeholder: "Hey Sagar" // updated value
readOnly: true //updated value
required: true //updated value
width: 200}
}
}
How can i Update this object in state (0th or 1st object dynamically)
object like
how can i do setState for inner loop of array of objects dynamically???
i have tried so many examples but no result ..please help me guyz
final output:
tabs: [{
name: "Step 1",
DroppedDetails: [
{id:1,name:Step1,
DroppedDetails:[
{DraggedItemRandomId: "70d19a9f-7e6e-4eb2-b974-03e3a8f03f08"
draggedItem:
category: "basic"
disabled: false
fieldClass: "Text"
height: 30
id: "text"
image: "/static/media/type.327c33c2.png"
label: "Text Field"
placeholder: "Hey Sagar" // updated value
readOnly: true //updated value
required: true //updated value
width: 200
}
{DraggedItemRandomId: "70d19a9f-7e6e-4eb2-b974-03e3a8f03f08"
draggedItem:
category: "basic"
disabled: false
fieldClass: "Text"
height: 30
id: "text"
image: "/static/media/type.327c33c2.png"
label: "Text Field"
placeholder: "Edit This Field"
readOnly: false
required: false
width: 200
}
]
},
{
name: "Step 2",
DroppedDetails: [
{DraggedItemRandomId: "70d19a9f-7e6e-4eb2-b974-03e3a8f03f08"
draggedItem:
category: "basic"
disabled: false
fieldClass: "Text"
height: 30
id: "text"
image: "/static/media/type.327c33c2.png"
label: "Text Field"
placeholder: "Edit This Field"
readOnly: false
required: false
width: 200
}]
}
],
You can get, first of all, the tabs of the state:
const { tabs } = this.state;
// here you code to decide what tab do you want to update
const tabSelected = tabs[0];
const { DroppedDetails } = tabSelected;
DroppedDetails[0]= {
name: "sagar111"
};
// You can edit another one or add a new one to the array also.
DroppedDetails[1]= {
name: "NameEdited"
};
DroppedDetails.push({ name: "New Name" })
And set state a new state:
this.setState(
{ tabs: tabs.map(t => t === tabSelected ? { ...tabSelected, DroppedDetails }) : t });
But it could be in this way too:
this.setState(tabs);
Because the original references were updated.
At the end DroppedDetails and tabs[0].DroppedDetails[0] are the same object.
how can i do setState for inner loop of array of objects???
It's not recommended the use of setState in a forloop.
Check this Calling setState in a loop only updates state 1 time
You can easily do that (this changes the name to lower case):
const { tabs } = this.state;
tabs.map(tab => {
// Do something with your tab value like the following
const newDroppedDetails = tab.map(({ name }) => ({ name: name.toLowerCase() });
return {
...tab,
DroppedDetails: newDroppedDetails
}
});
The key point here is to not mutate objects or arrays, but to create a new reference to objects that you modify, so the shallow compare on PureComponents will always work properly.
Applying the same concepts from your original codesandbox, we can do something like this to edit each individual Dropdown Detail.
working sandbox https://codesandbox.io/s/tab-creator-v2-8hc7c
import React from "react";
import ReactDOM from "react-dom";
import uuid from "uuid";
import "./styles.css";
class App extends React.Component {
state = {
tabs: [
{
id: 1,
name: "Tab 1",
content: "Wow this is tab 1",
DroppedDetails: [
{ name: "Bhanu", editing: false },
{ name: "Sagar", editing: false }
]
},
{
id: 2,
name: "Tab 2",
content: "Look at me, it's Tab 2",
DroppedDetails: [
{ name: "Christopher", editing: false },
{ name: "Ngo", editing: false }
]
}
],
currentTab: {
id: 1,
name: "Tab 1",
content: "Wow this is tab 1",
DroppedDetails: [
{ name: "Bhanu", editing: false },
{ name: "Sagar", editing: false }
]
},
editMode: false,
editTabNameMode: false
};
handleDoubleClick = () => {
this.setState({
editTabNameMode: true
});
};
handleEditTabName = e => {
const { currentTab, tabs } = this.state;
const updatedTabs = tabs.map(tab => {
if (tab.id === currentTab.id) {
return {
...tab,
name: e.target.value
};
} else {
return tab;
}
});
this.setState({
tabs: updatedTabs,
currentTab: {
...currentTab,
name: e.target.value
}
});
};
handleOnBlur = () => {
this.setState({
editTabNameMode: false
});
};
handleDetailChange = (e, id, index) => {
const { tabs, currentTab } = this.state;
const updatedCurrentTab = { ...currentTab };
updatedCurrentTab.DroppedDetails = updatedCurrentTab.DroppedDetails.map(
(detail, detailIndex) => {
if (index == detailIndex) {
return {
...detail,
name: e.target.value
};
} else {
return detail;
}
}
);
const updatedTabs = tabs.map(tab => {
if (tab.id == id) {
return {
...tab,
DroppedDetails: tab.DroppedDetails.map((detail, detailIndex) => {
if (detailIndex == index) {
return {
...detail,
name: e.target.value
};
} else {
return detail;
}
})
};
} else {
return tab;
}
});
this.setState({
tabs: updatedTabs,
currentTab: updatedCurrentTab
});
};
createTabs = () => {
const { tabs, currentTab, editTabNameMode } = this.state;
const allTabs = tabs.map(tab => {
return (
<li>
{editTabNameMode && currentTab.id === tab.id ? (
<input
value={tab.name}
onBlur={this.handleOnBlur}
onChange={this.handleEditTabName}
/>
) : (
<button
className={currentTab.id === tab.id ? "tab active" : "tab"}
onClick={() => this.handleSelectTab(tab)}
onDoubleClick={() => this.handleDoubleClick(tab)}
>
{tab.name}
</button>
)}
</li>
);
});
return <ul className="nav nav-tabs">{allTabs}</ul>;
};
handleSelectTab = tab => {
this.setState({
currentTab: tab,
editMode: false,
editTabNameMode: false
});
};
handleAddTab = () => {
const { tabs } = this.state;
const newTabObject = {
id: uuid(),
name: `Tab ${tabs.length + 1}`,
content: `This is Tab ${tabs.length + 1}`,
DroppedDetails: []
};
this.setState({
tabs: [...tabs, newTabObject],
currentTab: newTabObject,
editMode: false,
editTabNameMode: false
});
};
handleDeleteTab = tabToDelete => {
const { tabs } = this.state;
const tabToDeleteIndex = tabs.findIndex(tab => tab.id === tabToDelete.id);
const updatedTabs = tabs.filter((tab, index) => {
return index !== tabToDeleteIndex;
});
const previousTab =
tabs[tabToDeleteIndex - 1] || tabs[tabToDeleteIndex + 1] || {};
this.setState({
tabs: updatedTabs,
editMode: false,
editTabNameMode: false,
currentTab: previousTab
});
};
setEditMode = () => {
this.setState({
editMode: !this.state.editMode
});
};
handleContentChange = e => {
const { tabs, currentTab } = this.state;
const updatedTabs = tabs.map(tab => {
if (tab.name === currentTab.name) {
return {
...tab,
content: e.target.value
};
} else {
return tab;
}
});
this.setState({
tabs: updatedTabs,
currentTab: {
...currentTab,
content: e.target.value
}
});
};
handleOnDetailBlur = (id, index) => {
const { tabs, currentTab } = this.state;
const updatedCurrentTab = { ...currentTab };
updatedCurrentTab.DroppedDetails = updatedCurrentTab.DroppedDetails.map(
(detail, detailIndex) => {
if (index == detailIndex) {
return {
...detail,
editing: false
};
} else {
return detail;
}
}
);
const updatedTabs = tabs.map(tab => {
if (tab.id == id) {
return {
...tab,
DroppedDetails: tab.DroppedDetails.map((detail, detailIndex) => {
if (detailIndex == index) {
return {
...detail,
editing: false
};
} else {
return detail;
}
})
};
} else {
return tab;
}
});
this.setState({
tabs: updatedTabs || [],
currentTab: updatedCurrentTab
});
};
handleDoubleClickDetail = (id, index) => {
const { tabs, currentTab } = this.state;
const updatedCurrentTab = { ...currentTab };
updatedCurrentTab.DroppedDetails = updatedCurrentTab.DroppedDetails.map(
(detail, detailIndex) => {
if (index == detailIndex) {
return {
...detail,
editing: true
};
} else {
return detail;
}
}
);
const updatedTabs = tabs.map(tab => {
if (tab.id == id) {
return {
...tab,
DroppedDetails: tab.DroppedDetails.map((detail, detailIndex) => {
if (detailIndex == index) {
return {
...detail,
editing: true
};
} else {
return detail;
}
})
};
} else {
return tab;
}
});
this.setState({
tabs: updatedTabs || [],
currentTab: updatedCurrentTab
});
};
createContent = () => {
const { currentTab } = this.state;
return (
<div>
<div>
<p>{currentTab.content}</p>
<div>
<h4>Dropped Details</h4>
{currentTab.DroppedDetails ? (
<div>
{currentTab.DroppedDetails.map((detail, index) => {
if (detail.editing) {
return (
<div>
<input
value={detail.name}
onChange={e =>
this.handleDetailChange(e, currentTab.id, index)
}
onBlur={() =>
this.handleOnDetailBlur(currentTab.id, index)
}
/>
</div>
);
} else {
return (
<p
onDoubleClick={() =>
this.handleDoubleClickDetail(currentTab.id, index)
}
>
{detail.name}
</p>
);
}
})}
</div>
) : (
""
)}
</div>
</div>
{currentTab.id ? (
<div style={{ display: "flex", justifyContent: "space-between" }}>
<button className="edit-mode-button" onClick={this.setEditMode}>
Edit
</button>
<button onClick={() => this.handleDeleteTab(currentTab)}>
Delete
</button>
</div>
) : (
""
)}
</div>
);
};
render() {
const { currentTab, editMode } = this.state;
return (
<div className="container">
<div className="well">
<button className="add-tab-button" onClick={this.handleAddTab}>
<i className="text-primary fas fa-plus-square" /> Add Tab
</button>
{this.createTabs()}
<div className="tab-content">
{editMode ? (
<div>
<textarea
onChange={this.handleContentChange}
value={currentTab.content}
/>
<button className="save-button" onClick={this.setEditMode}>
Done
</button>
</div>
) : (
this.createContent()
)}
</div>
</div>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
To activate "edit-mode" double-click a dropped-detail. The input should appear in its place and you can type in the new text. When complete, click out of the input and it will finalize the updated text :)