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>
</>
}
Related
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
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>
))
import { useState } from "react";
function App() {
const [itemsClicked, setItemsClicked] = useState([]);
const dataList = [
{ id: 1, name: "jake" },
{ id: 12, name: "edd" },
{ id: 13, name: "john" }
];
const highlight = (data) => {
setItemsClicked((array) => {
let itemClicked = array.includes(data)
? array.filter((x, i) => x.id !== data.id)
: [...array, data]; // What I'm trying to do here is to add a new field which is 'active' >> [...array, {...data, active: true}];
return itemClicked;
});
};
return (
<div>
{dataList.map((item, i) => (
<div
style={{
borderColor: itemsClicked[i]?.active ? "1px solid green" : ""
}}
>
<button onClick={() => highlight(dataList[i])}>HIGHLIGHT</button>
</div>
))}
</div>
);
}
export default App;
What I'm trying to do here is to create a toggle which is to highlight the div.
but my problem is on the state instead of it will remove the data object, it will continue appending. how to fix it?
for example when I try to click the first data which is jake, the output should be like this in itemsClicke
[{ id: 1, name: jake, active: true }]
but when I try to click again it will just remove it from the list, but on my side, it continuing to append the data which is wrong
[{ id: 1, name: jake, active: true },{ id: 1, name: jake, active: true },{ id: 1, name: jake, active: true },{ id: 1, name: jake, active: true }]
You can simply remove the itemsClicked state and put the dataList in a state variable to have control over it:
const [dataList, setDataList] = useState([
{ id: 1, name: "jake" },
{ id: 12, name: "edd" },
{ id: 13, name: "john" }
]);
Now, you need to check an isActive property to conditionally add some styles, so you need isActive but it's not in the current dataList and you created itemsClicked to solve this issue. But there are some simpler solutions like adding a property on the fly with your dataList.
You can implement toggleHighlight function to change isActive property:
const toggleHighlight = (id) => {
setDataList((prevState) =>
prevState.map((item) =>
item.id === id ? { ...item, isActive: !item.isActive } : item
)
);
};
this toggleHandler will accept an id and take a for loop over the dataList, it finds the clicked item. if it's an active item so it changes it to false and vice versa.
let's recap and put all things together:
import { useState } from "react";
function App() {
const [dataList, setDataList] = useState([
{ id: 1, name: "jake" },
{ id: 12, name: "edd" },
{ id: 13, name: "john" }
]);
const toggleHighlight = (id) => {
setDataList((prevState) =>
prevState.map((item) =>
item.id === id ? { ...item, isActive: !item.isActive } : item
)
);
};
return (
<div>
{dataList.map((item) => (
<div
key={item.id}
style={{
display: "flex",
paddingTop: 10,
border: item.isActive ? "2px solid green" : ""
}}
>
<p style={{ padding: 5 }}>{item.name}</p>
<button onClick={() => toggleHighlight(item.id)}>HIGHLIGHT</button>
</div>
))}
</div>
);
}
export default App;
Try this on a live playground
array.includes(data)
won't return true because of this: https://stackoverflow.com/a/50371323/17357155
Just use this. It's a simple example. I used itemsClicked only for storing the ids
const highlight = (data) => {
if(itemsClicked.indexOf(data.id) == -1)
{
setItemsClicked(prevData => {
return [...prevData, data.id]
})
}
}
{
dataList.map((item, i) => (
<div
style={{
border:
itemsClicked.indexOf(item.id) > -1 ? '1px solid green' : '',
}}
>
{itemsClicked.indexOf(item.id) > -1 ? '1px solid green' : ''}
<button onClick={() => highlight(dataList[i])}>HIGHLIGHT</button>
</div>
))
}
Now I do not really understand you. Sorry, I just started this whole study not so long ago. I’ll try to explain again what I can’t do.
I have an empty object and an object with data with the same structure.
data: [
{id: 1, title: "title1"},
{id: 2, title: "title1"},
{id: 3, title: "title3"},
{id: 4, title: "title4"},
{id: 5, title: "title3"}
],
item: [
{
itemId: "",
itemname: ""
}
]
And I have select and textarear. Select have data, textarear empty. Textarear displays title.
I want to press a button. Selected item from select. copied to textarear (title only), and also itemId - this selected element id: 5 and itemname - the same title: "title3" element, was recorded in item [].
https://codesandbox.io/s/priceless-hermann-g9flw
Please do check now
import React from "react";
class App extends React.Component {
constructor() {
super();
this.state = {
id: null,
title: "",
filmItem: "",
listFilms: [],
data: [
{ id: 1, title: "title1" },
{ id: 2, title: "title2" },
{ id: 3, title: "title3" },
{ id: 4, title: "title4" }
],
item: []
};
this.onChange = this.onChange.bind(this);
this.onChangeArea = this.onChangeArea.bind(this);
this.addFilm = this.addFilm.bind(this);
this.choice = this.choice.bind(this);
}
addFilm(film) {
const selectedData = this.state.data.find(item => item.id == film);
console.log(selectedData);
this.setState({
listFilms: [...this.state.listFilms, selectedData.title],
item: [
...this.state.item,
{ itemId: selectedData.id, itemname: selectedData.title }
]
});
}
onChange = e => {
this.setState({ [e.target.name]: e.target.value });
};
onChangeArea = e => {
this.setState({ [e.target.name]: e.target.value.split("\n") });
};
choice(title) {
this.setState({ filmItem: title });
}
render() {
return (
<div className="App">
<div className="row App-main">
<div>
<select name="filmItem" size="4" onChange={e => this.onChange(e)}>
{this.state.data.map(film => (
<option key={film.title} value={film.id}>
{film.title}
</option>
))}
</select>
</div>
<div>
<button
className="editButton"
onClick={() => this.addFilm(this.state.filmItem)}
>
button
</button>
</div>
<div>
<textarea
name="films"
onChange={this.onChangeArea}
value={this.state.listFilms.map(r => r).join("\n")}
/>
</div>
<div>
<input type="text" name="text-input" onChange={this.onChange} />
</div>
</div>
<pre style={{ whiteSpace: "pre-wrap" }}>
{JSON.stringify(this.state)}
</pre>
</div>
);
}
}
export default App;
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 :)