I am working on a building a quiz app. I have already built it. but so far I am not happy with my code. I believe the code is not scalable and concise enough. I was wondering if you could help me make my code better.
what I need is, when the user clicks one of the options, if it is correct, then make the button clicked into green background. otherwise, red and correct buttons into green. for now, it is working as it should. but lets say down the road, I have to have questions that contains more than 2 options like 3, 5, or even more. then, this code will not work. and the logic gets even uglier.
Could you please help me out of it?
const questions = [
{
id: 1,
question: "which one of them is fruit?",
options: ["apple", "rice"],
answers: ["correct", "wrong"]
},
]
function App() {
const [btnA, setBtnA] = React.useState("gray")
const [btnB, setBtnB] = React.useState("gray")
const handleButtonA = (e, answer) => {
e.preventDefault()
if(answer == "correct") {
setBtnA("green");
}
if(answer === "wrong") {
setBtnA("red")
setBtnB("green")
}
}
const handleButtonB = (e, answer) => {
e.preventDefault();
if(answer === "correct") {
setBtnB("green")
}
if(answer === "wrong") {
setBtnA("green")
setBtnB("red")
}
}
return (
<div className="App">
{questions.map(q => {
return (
<div key={q.id}>
<h3>{q.question}</h3>
<button style={{background: btnA}} onClick={e => handleButtonA(e, q.answers[0])}>{q.options[0]}</button>
<button style={{background: btnB}} onClick={e => handleButtonB(e, q.answers[1])}>{q.options[1]}</button>
</div>
)
})}
</div>
);
}
First of all, your code won't work with multiple questions. Since you share the same state for all buttons, clicking the right/wrong button will update all of them.
2nd of all, I'd recommend you change from this:
options: ["apple", "rice"],
answers: ["correct", "wrong"]
to:
options: [
"apple",
"rice",
],
correct: 0
Where correct is the index is of the correct option. This way you don't have to check for a string, and in general, it is more oriented.
Now, we dont need to know the colors in the state, we just need to know if the question has been attempted or not. For that, we can create a state which is an array.
const [quizData, setQuizData] = useState(questions.map(question => {
return {
attempted: -1
}
}))
Now finally, we can loop over each element in the array.
return (
<div className="App">
{questions.map((q, questionIndex) => {
const currData = quizData[questionIndex];
// Method to handle when button is clicked
// valid is weather the question clicked was valid
const handleClick = (index) => {
// we use a method which recieves the currentState when using setState
// in order to prevent react scheduling issues
setQuizData((prevData) => {
// for the current question, set attempted to true
prevData[questionIndex].attempted = index;
// destructure the array because react doesnt work well with returning the same object instance
return [
...prevData
]
})
}
return (
<div key={q.id}>
<h3>{q.question}</h3>
{/* Looop over each option */}
{question.options.map((option, index) => {
// take gray as the default color
let color = "gray";
// if the user has attempted already, check to change the color
if (currData.attempted >= 0) {
if (index == currData.attempted) {
if (index == q.correct) {
color = "green";
}
else {
color = "red"
}
}
else if (index == q.correct) {
color = "greeen"
}
}
// over here the button's enabled property is something I added, but you can remove that if you'd like
return (
<button
enabled={!currData.attempted}
key={index}
style={{background: color}}
onClick={() => handleClick(index)}>
{option}
</button>
)
}}
</div>
)
})}
</div>
);
So in the end, our code is:
const questions = [
{
id: 1,
question: "which one of them is fruit?",
options: [
"apple",
"rice",
],
correct: 0
},
]
function App() {
const [quizData, setQuizData] = useState(questions.map(question => {
return {
attempted: -1
}
}))
return (
<div className="App">
{questions.map((q, questionIndex) => {
const currData = quizData[questionIndex];
// Method to handle when button is clicked
// valid is weather the question clicked was valid
const handleClick = (index) => {
// we use a method which recieves the currentState when using setState
// in order to prevent react scheduling issues
setQuizData((prevData) => {
// for the current question, set attempted to true
prevData[questionIndex].attempted = index;
// destructure the array because react doesnt work well with returning the same object instance
return [
...prevData
]
})
}
return (
<div key={q.id}>
<h3>{q.question}</h3>
{/* Looop over each option */}
{question.options.map((option, index) => {
// take gray as the default color
let color = "gray";
// if the user has attempted already, check to change the color
if (currData.attempted >= 0) {
if (index == currData.attempted) {
if (index == q.correct) {
color = "green";
}
else {
color = "red"
}
}
else if (index == q.correct) {
color = "greeen"
}
}
// over here the button's enabled property is something I added, but you can remove that if you'd like
return (
<button
enabled={!currData.attempted}
key={index}
style={{background: color}}
onClick={() => handleClick(index)}>
{option}
</button>
)
}}
</div>
)
})}
</div>
);
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.