Toggle correct answer message with radio button in React - javascript

Given a small quiz app, I'm trying to toggle a message under the questions to show the user that they have selected the correct answer or incorrect answer.
I have a small message that I'm gating with a boolean value which is set by the selection of the radio button (though the correct radio button isn't getting selected on initial click for some reason, only incorrect answers are), and need it to show a message of "You got it right" if the answer is true or "incorrect" if false. I can show true, but the logic isn't working to show false if the answer is incorrect and then clear the messaging when the "next question" button is clicked.
function App() {
let [points, setPoints] = useState(null);
let [counter, setCounter] = useState(null);
let [question, setQuestions] = useState();
let [isCorrect, setIsCorrect] = useState(false); <==Store correct answer selected
function Answer(props) {
return (
<li aria-labelledby="answers-list">
<label>
<input
type="radio"
name="answer_group"
className="answer"
value={props.answer}
onChange={checkAnswer}
/>
{props.answer}
</label>
</li>
);
}
function checkAnswer(e) {
let val = e.target.value;
let ans = question[counter].answers.filter((ans) => ans.value === val)[0];
displayCorrect(ans.correct);
//Toggles message to true if answer is correct
ans.correct === true ? setIsCorrect(true) : setIsCorrect(false);
}
function Quiz(props) {
return (
<div className="quiz">
<div className="quesiton" role="h2">
{props.question}
</div>
<ul className="answers">{props.children}</ul>
</div>
);
}
function displayCorrect(correct) {
let correct_msg = correct ? "correct" : "incorrect";
console.log("Answer was " + correct_msg);
}
function nextQuestion() {
setIsCorrect(false); <== Should hide message when user selects "next question" button
if (document.querySelector('input[name="answer_group"]:checked') == null) {
alert("Must select an answer before proceeding to the next question");
return;
}
let val = document.querySelector('input[name="answer_group"]:checked')
.value;
let answerObj = question[counter].answers.filter(
(ans) => ans.value === val
)[0];
let updated_points = answerObj.correct ? points + 1 : points;
setPoints(updated_points);
let nextQuestion = counter + 1;
if (counter < question.length - 1) {
setCounter(nextQuestion);
} else {
setCounter(0);
}
displayCorrect(answerObj.correct);
}
return (
<div className="Quiz slide-top">
{!is_started ? (
<div className="start-intro-wrapper">
<h1 className="name">Quiz App</h1>
<Starter start={start} />
</div>
) : (
<div className="quick-wrapper slide-in-bottom">
<Quiz question={question[counter].question}>
{question[counter].answers.map((answer, index, arr) => {
return (
<Answer
key={index}
index={index}
answer={answer.value}
correct={answer.correct}
/>
);
})}
</Quiz>
<div className="answer-controls">
{isCorrect === true ? <p>You got it right!</p> : null} <== Messaging should show if user has selected correct message if correct option is selected
<button onClick={(e) => nextQuestion()}>
Next question
</button>
</div>
</div>
)}
</div>
);
}
Codesandbox Link

It doesn't display anything because you put null in the other condition for the display.
If you replace it with {isCorrect === true ? <p>You got it right!</p> : <p>You got it wrong!</p>} the correct message is displayed.
You can also remove completly the function displayCorrect it's only writing in the console.
If you want to clear the message when the user click on "next question" you could do it with a new variable hasAnswered set at false by default.
You set it to true in your checkAnswer function, and to false in your nextQuestion function.
The code for the text at the bottom would then looks like:
{hasAnswered &&
(isCorrect === true ? <p>You got it right!</p> : <p>You got it wrong!</p>)
}
Regarding the issue with you needing to click twice to update the radio button selected, i think it's a re-render issue when isCorrect change.
To avoid the issue, you could store the value checked by the user and use this to display if the radio should be checked or no.
It works with the following changes
let [isChecked, setChecked] = useState(null);
[...]
function Answer(props) {
return (
<li aria-labelledby="answers-list">
<label>
<input
checked={isChecked===props.answer}
type="radio"
name="answer_group"
className="answer"
value={props.answer}
onChange={checkAnswer}
/>
{props.answer}
</label>
</li>
);
}
function checkAnswer(e) {
let val = e.target.value;
let ans = question[counter].answers.filter((ans) => ans.value === val)[0];
setIsCorrect(ans.correct);
setChecked(ans.value);
}
You also need setChecked(null) in your nextQuestion function to reset everything.

Related

Formik instant feedback input box

I'm trying to make a input box component that has instant feedback using Formik. I want the input box to turn green when the user input matches a predefined string (the "answer"), gray if the input matches the prefix of the answer (including the empty string) and red otherwise. This string is stored as a property of the initial values, values.answer. The Formik validate function checks if the input equals values.answer and sets values.correct = true. I then created a css class corresponding to a green input box and set the className of the input conditional on the value of values.correct. The problem is it only seems to update (i.e turn green with a correct input) when I click out of focus of the input box (i.e onBlur). I would like it to work onChange. How would I do this?
Here is the relevant code sandbox: https://codesandbox.io/s/instant-feedback-box-lub0g?file=/src/Frame.js
Cool problem, but you've overcomplicated your code a little bit 😉 Some feedback:
touched is set to true during onBlur by default. You can override this by using setTouched(), but I found it simpler to just use values instead of touched in your form
try to keep values as minimal as possible, it's only meant to access input values so there's no need for hint and answer to be assigned to it
the purpose of the validation function is to return an errors object and not to set values, so remove assignments like values.correct = true
You don't need to store isDisabled in state, you can derive it from formik.submitCount and formik.isSubmitting
const Note = () => {
const [showFrame, setShowFrame] = useState({ 1: true });
const onCorrectSubmission = (frameId) => {
setShowFrame({ ...showFrame, [frameId]: true });
};
const text =
"What is the sum of the first three natural numbers? (give answer as a word, i.e one, two etc.)";
const hint = "The first three natural numbers are 1, 2, and 3";
const answer = "six";
return (
<div>
<h1>Induction</h1>
{showFrame[1] ? (
<Frame
id={1}
text={text}
hint={hint}
answer={answer}
onCorrectSubmission={onCorrectSubmission}
/>
) : null}
{showFrame[2] ? (
<Frame
id={2}
text={text}
hint={hint}
answer={answer}
onCorrectSubmission={onCorrectSubmission}
/>
) : null}
</div>
);
};
const Frame = ({
id,
text,
hint,
answer,
values,
onCorrectSubmission,
...props
}) => {
const validate = (values) => {
const errors = {};
if (!answer.startsWith(values.cloze)) {
errors.cloze = hint;
} else if (values.cloze !== answer) {
errors.cloze = true;
}
return errors;
};
const formik = useFormik({
initialValues: {
cloze: ""
},
validate,
onSubmit: (values) => {
onCorrectSubmission(id + 1);
}
});
const isFinished = formik.isSubmitting || formik.submitCount > 0;
return (
<form enablereinitialize={true} onSubmit={formik.handleSubmit}>
<p>{text}</p>
<input
id="cloze"
name="cloze"
type="text"
autoComplete="off"
{...formik.getFieldProps("cloze")}
disabled={isFinished}
className={`input
${!answer.startsWith(formik.values.cloze) ? "invalid-input" : ""}
${formik.values.cloze && !formik.errors.cloze ? "valid-input" : ""}
`}
/>
{formik.values.cloze && formik.errors.cloze ? (
<div>{formik.errors.cloze}</div>
) : null}
<button disabled={!!formik.errors.cloze || isFinished} type="submit">
Submit
</button>
</form>
);
};
export default Frame;
Live Demo

How can delivered array in event function on javascript with React?

There is two screen. First picture is show list. If clicked Button which is next to search Button, second picture is showed. Second is select filter. When user select options in filter and clicked "적용" Button, list will be changed.
And there code is here.
...
const [modalOpen, setModalOpen] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
const [postsPerPage] = useState(6);
const [area] = useState(location.state.area)
const [city] = useState(location.state.city)
// all list
const locList = restList.filter(
key => (key.address.includes(area) && key.address.includes(city))
);
//about Modal
const openModal = () => {
setModalOpen(true);
}
const closeModal = () => {
setModalOpen(false);
}
...
let target;
let foodList = [[], []]
const sel_Food = (e) => {
if (e.target.tagName === "BUTTON") {
target = e.target.innerText;
if (!foodList.includes(target))
foodList[0].push(target)
} else {
target = e.target.parentNode.nextSibling.innerText;
foodList[1] = target
}
}
//make list
const list = (foodList) => {
let result = [];
if (foodList[0].length === 1) {
result = locList.filter(key => key.foodType.includes(foodList[0][0]))
} else {
for (let i = 0; i < foodList[0].length; i++) {
let tmp = locList.filter(key => key.foodType.includes(foodList[0][i]))
result.push(...tmp)
}
}
if (foodList[1].length != 0) {
result = result.filter(key => key.friendly.includes(foodList[1]))
}
return result
}
return (
<div>
... {
(locList.length === 0)
? ...
: <div>
<div className="btn_class_list">
...
<React.Fragment>
<button className="button_filter" onClick={openModal}><img className="icon_filter" src={icon_filter} alt="icon_filter"/></button>
<Modal_Restaurant open={modalOpen} close={closeModal} header="필터">
<p className="langoption">음식 종류</p>
<div>
...
</div>
<p className="langoption">Halal Standard</p>
<div className="foodtype">
...
</div>
</Modal_Restaurant>
</React.Fragment>
</div>
<Item rlist={currentPosts(locList)} moveTo="restaurant" area={area} city={city}></Item> //make List
<Pagination start={numOfFirst} last={numOfLast} paginate={setCurrentPage}></Pagination>
</div>
}
</div>
)
sel_Food function record option that user selected.
list function make sub list for delivered to Item component.
But I don't know how to delivered array result in list function with "적용" button.
Thank you.
As I understand from the question, you need to change the list of items you display according to the filters the user applies. It can be done with the state. As a start, you can try to update the list of items through a filter function and use the state variable to render the filtered results. You'd asked about returning a value when a button is clicked. Buttons have an onClick handler in React. You can pass it a function like this:
<Button onClick={handleClick} />
or if your function takes any arguments:
<Button onClick={() => handlClick(args)} />

can't read else part

I want my divs to check/uncheck.
But, else part can't read my code (undefined)
Plz help me...
(First time click is fine
second time click is undefined)
import React, { useState } from "react";
import "./Blocking.css";
const Blocking = () => {
const [checked, setChecked] = useState([false, false, false, false, false]);
const onCheck = (e) => {
e.preventDefault();
console.log(e.target.getAttribute("name"));
if (checked[e.currentTarget.getAttribute("name")] === false) {
e.target.style.background = "aquamarine";
setChecked(!checked[e.target.getAttribute("name")]);
console.log("if " + checked[e.target.getAttribute("name")]);
} else {
e.target.style.background = "white";
setChecked(!checked[e.target.getAttribute("name")]);
console.log("else " + checked[e.target.getAttribute("name")]);
}
};
return (
<div className="container">
<div className="items" name="0" onClick={onCheck}>
1
</div>
<div className="items" name="1" onClick={onCheck}>
2
</div>
<div className="items" name="2" onClick={onCheck}>
3
</div>
<div className="items" name="3" onClick={onCheck}>
4
</div>
<div className="items" name="4" onClick={onCheck}>
5
</div>
</div>
);
};
export default Blocking;
Below code block is console.log in chrome)
[HMR] Waiting for update signal from WDS...
Blocking.jsx:9 2
Blocking.jsx:14 if false
Blocking.jsx:9 2
Blocking.jsx:18 else undefined
1st i click any blocks then, print fine.
2nd i click any blocks then, print undefined.
The error is in:
setChecked(!checked[e.target.getAttribute("name")]);
The "checked" array is being replaced by a boolean.
You could do it as:
let newChecked = [...checked]; //Destructure
let index = e.currentTarget.getAttribute("name") //Get index
newChecked[index] = !checked[index] //Toggle check
setChecked(newChecked)
Issues
You are mutating your checked state array. setChecked(!checked[e.target.getAttribute("name")]); mutates the state to a single boolean, so subsequent checked[e.currentTarget.getAttribute("name")] conditional tests will likely always be falsey.
You are also directly manipulating the DOM. This is an anti-pattern in React.
You need to shallow copy the entire array and update the specific index. Use the state value to derive the background color.
const onCheck = (e) => {
e.preventDefault();
const index = Number(e.currentTarget.getAttribute("name"));
setChecked(checked => checked.map((val, i) => i === index ? !val : val));
};
return (
<div className="container">
<div
className="items"
style={{ background: checked[0] ? 'white' : 'aquamarine' }}
name="0"
onClick={onCheck}
>
1
</div>
...etc
</div>
);

why is my setstate function is not working for the first call?

there is a food function with props that is type which is equal to 'Lunch' or 'Dinner'
I need to change the value of LunchStatus when submit is clicked according to the condition on type
const Food = (props) =>{
const [LunchStatus, LunchUpdate] = useState('Lunch YES');
const [DinnerStatus, DinnerUpdate] = useState('Dinner YES');
function handlingSubmit(e){
if(props.type === 'Lunch'){
LunchUpdate('Lunch NO');
console.log(LunchStatus);
}
else{
DinnerUpdate('Dinner NO');
console.log(DinnerStatus);
}
}
return (
<div className='food-box'>
<button class="button_raise" onClick={handlingSubmit}>Submit</button>
</div>
);
}
and output is showing Lunch YES and Dinner YES for first clicks and Lunch NO and Dinner NO for remaining clicks
the output is like when I click both one after one continuously is
Lunch YES
Dinner YES
Lunch NO
Dinner NO
Lunch NO
Dinner NO
There are a couple of reasons for this behavior:
: State is updated asynchronously.
: In any particular render, state and props don't change, changes are only reflected when the component re-renders.
const Food = (props) => {
const [LunchStatus, LunchUpdate] = useState('Lunch YES');
const [DinnerStatus, DinnerUpdate] = useState('Dinner YES');
useEffect(() => {
console.log(LunchStatus);
}, [LunchStatus])
useEffect(() => {
console.log(DinnerStatus);
}, [DinnerStatus])
function handlingSubmit(e) {
if (props.type === 'Lunch') {
LunchUpdate('Lunch NO');
}
else {
DinnerUpdate('Dinner NO');
}
}
return (
<div className='food-box'>
<button class="button_raise" onClick={handlingSubmit}>Submit</button>
</div>
);
}
Your status was updated.
Just you are not seeing because the state is applied after that.
<div className='food-box'>
<p>Lanch state: {{LunchStatus}}</>
<p>Lanch state: {{DinnerStatus}}</>
<button class="button_raise" onClick={handlingSubmit}>Submit</button>
</div>
or
const Food = (props) =>{
const [LunchStatus, LunchUpdate] = useState('Lunch YES');
const [DinnerStatus, DinnerUpdate] = useState('Dinner YES');
function handlingSubmit(e){
if(props.type === 'Lunch'){
LunchUpdate('Lunch NO');
}
else{
DinnerUpdate('Dinner NO');
}
}
console.log(LunchStatus);
console.log(DinnerStatus);
return (
<div className='food-box'>
<button class="button_raise" onClick={handlingSubmit}>Submit</button>
</div>
);
}
Because you are not updating YES at any click, you are only updating NO . Only time it is printing YES is when its initialized with useState('Dinner YES')

React - Input not working when using onChange and onKeyDown

I tried to implement something like a multi-select, where the user can either select a value from a data list or can type in a new value. A chosen value should be added to an array if the user presses enter. For detecting changes in the input field I use onChange and a state variable that saves the current value typed in. For detecting the press of enter I use onKeyDown. The problem is that I'm no longer able to type something in the input field, however choosing values from the data list works. I figured out that when I comment out onKeyDown, I can type something in the input field and can also choose from values provided by the data list. However, in this case, adding values to an array on the press of enter doesn't work. I'm fairly new to React, is there something I miss?
My current code looks like follows:
const EditableMultiSelect = ({ field, helpers, metadataField, editMode, setEditMode }) => {
const { t } = useTranslation();
const [inputValue, setInputValue] = useState('');
const handleChange = e => {
const itemValue = e.target.value;
setInputValue(itemValue);
}
const handleKeyDown = event => {
event.preventDefault();
if (event.keyCode === 13) {
field.value[field.value.length] = inputValue;
helpers.setValue(field.value);
setInputValue("");
}
}
const removeItem = () => {
console.log('to be implemented');
}
return (
editMode ? (
<>
<div
onBlur={() => setEditMode(false)}
ref={childRef}>
<input name="inputValue"
value={inputValue}
type="text"
onKeyDown={e => handleKeyDown(e)}
onChange={e => handleChange(e)}
placeholder={t('EDITABLE.MULTI.PLACEHOLDER')}
list="data-list"
/>
<datalist id="data-list">
{metadataField.collection.map((item, key) => (
<option key={key}>{t(item.value)}</option>
))}
</datalist>
</div>
{(field.value instanceof Array && field.value.length !== 0) ? (field.value.map((item, key) => (
<span className="ng-multi-value"
key={key}>
{t(item)}
<a onClick={() => removeItem(key)}>
<i className="fa fa-times" />
</a>
</span>
))) : null}
</>
) : (
<div onClick={() => setEditMode(true)}>
{(field.value instanceof Array && field.value.length !== 0) ? (
<ul>
{field.value.map((item, key) => (
<li key={key}>
<span>{item}</span>
</li>
))}
</ul>
) : (
<span className="editable preserve-newlines">
{""}
</span>
)}
<i className="edit fa fa-pencil-square"/>
</div>
)
);
};
You're calling event.preventDefault() every time a key is pressed. You should move it inside the if statement:
const handleKeyDown = event => {
if (event.keyCode === 13) {
event.preventDefault();
field.value[field.value.length] = inputValue;
helpers.setValue(field.value);
setInputValue("");
}
}
you can't type anything anymore in the input text because in the handleKeyDown event handler, you're calling event.preventDefault() in the early lines. So i think you just have to move it into the if case:
const handleKeyDown = event => {
if (event.keyCode === 13) {
event.preventDefault();
field.value[field.value.length] = inputValue;
helpers.setValue(field.value);
setInputValue("");
}
}
Remove e.preventDefault() or put it inside the if statements.
It is the one preventing the input from being editable.

Categories