I'm working on an exercise in which I have to build a simple Quiz App,
I have a few components (such as "question", "question-list" and "add-question"),
in the "add-question" component I have a form to add a new question to the list and using a template we saw in class I'm trying to add the values the user adds to my Questions List,
I succeeded in doing that with the regular parameters by naming the relevant input with the same "name" as the parameter name,
but I'm trying to pass some values from the form into my "answers" array (which contains "text" (string), "correctAnswer"(boolean),
I tried a few things but none of them worked and the last one I tried works only for the last input I filled but not for all 4
That's my code (Including my "question" format, the "onChange" function and the form):
function AddQuestion(props) {
const [question, SetQuestion] = useState({
title: "",
description: "",
answers: [
{ id: 0, text: "", correctAnswer: false }
{ id: 1, text: "", correctAnswer: false },
{ id: 2, text: "", correctAnswer: false },
{ id: 3, text: "", correctAnswer: false }],
clicked: false});
const onChange = (e) => {
let firstAnswer, secondAnswer, thirdAnswer, fourthAnswer;
if (e.target.name === "firstAnswer") {
firstAnswer = e.target.value;
}
if (e.target.name === "secondAnswer") {
secondAnswer = e.target.value;
}
if (e.target.name === "thirdAnswer") {
thirdAnswer = e.target.value;
}
if (e.target.name === "fourthAnswer") {
fourthAnswer = e.target.value;
}
let updated = {...question, answers: {
0: { text: firstAnswer },
1: { text: secondAnswer },
2: { text: thirdAnswer},
3: { text: fourthAnswer}}
};
updated[e.target.name] = e.target.value;
SetQuestion(updated);
};
return (
<div className={style.newQuestionForm}>
<h1>Add a New Question</h1>
<label>Title: <br /> <input type="text" name="title" onInput={onChange}/></label>
<label>Description:<br/><input type="text" name="description" onInput={onChange}/></label>
<label>First Answer:<br /><input type="text" name="firstAnswer" onInput={onChange}/></label>
<label>Second Answer:<br/><input type="text" name="secondAnswer" onInput={onChange}/></label>
<label>Third Answer:<br/><input type="text" name="thirdAnswer" onInput={onChange}/></label>
<label>Fourth Answer:<br/><input type="text" name="fourthAnswer" onInput={onChange}/></label>
<div className={style.btnDiv}>
<button className={style.newQuestionBtn} onClick={() => props.onAdd(question)}>
Add New Question
</button>
</div>
</div>
);
}
There was a problem when you are trying to save final object to state.
As you can see, you are trying to convert answersfrom array to dictionary:
let updated = {...question, answers: {
0: { text: firstAnswer },
1: { text: secondAnswer },
2: { text: thirdAnswer},
3: { text: fourthAnswer}}
};
In addition, you are losing more information about the answers, like id and correctAnswer
To solve this you must continue using an array instead of dictionary.
Furthermore I improved about the logic to save info to state.
I deleted variables about the answers to use unique object, updatedQuestion. This object contains previous value from state and then you can use your conditionals to check which answer it is and change only his text
const onChange = (e) => {
let updatedQuestion = {
...question
};
if (e.target.name === "firstAnswer") {
updatedQuestion.answers[0].text = e.target.value;
}
if (e.target.name === "secondAnswer") {
updatedQuestion.answers[1].text = e.target.value;
}
if (e.target.name === "thirdAnswer") {
updatedQuestion.answers[2].text = e.target.value;
}
if (e.target.name === "fourthAnswer") {
updatedQuestion.answers[3].text = e.target.value;
}
SetQuestion(updatedQuestion);
};
Now if you try this method, when you add a question to your app, form works. You just need an extra checkbox to select which question is correct.
Let me know if you have any problem
Related
I have tried to enter questionText by inputFields.questions.questionText, but it is not working
const [inputFields, setInputFields] = useState([
{
sectionName: "",
sectionDesc: "",
questions: [{ questionType: "", questionText: "" }],
},
]);
<input value={inputFields.questions.questionText} />
1)
questions and inputFields are arrays not an object so you should use index to access its value as:
value={inputFields[0].questions[0].questionText}
2) If you want to change input value on input of value then you have to use onChange here as:
CODESANDBOX LINK
function onChangeInput(e) {
setInputFields((currValue) => {
const clone = [...currValue];
clone[0].questions[0].questionText = e.target.value;
return clone;
});
}
I am trying to take input from user and push that input into array of object . It working fine but I face one problem . When I type for exmaple ( Nine ) so it created 4 object inside array . I want only single object and store user value.
It created an array like
[
{name : 'text', value : 'N'}
{name : 'text', value : 'Ni'}
{name : 'text', value : 'Nin'}
{name : 'text', value : 'Nine'}
]
Could someone please help me how to resolve this issue. Thanks
Code
<input
type="text"
className="inputStyle"
placeholder={item.fieldName}
onChange={(e) =>
this.generateExtraFieldData(
e.target.value,
item.fieldName
)
}
/>
generateExtraFieldData = (data, type) => {
const { optionalFields } = this.state;
var joined = optionalFields.concat({ name: "text", value: data });
this.setState({
optionalFields: joined,
});
};
You don't need to join or concat the fields yourself, you can simply use:
this.setState({
optionalFields: {name:'text', value: data},
});
Ideally, you can consider that when the user stop typing, he won't insert a new character, so basically you can store only the most recent value and replace it every time:
<input
type="text"
className="inputStyle"
placeholder={item.fieldName}
onChange={(e) =>
this.generateExtraFieldData(
e.target.value,
item.fieldName
)}
/>
generateExtraFieldData = (data, type) => {
this.setState({
optionalFields: { name: "text", value: data },
});
};
I believe you should listen to onBlur event but rather waiting for the user to stop typing. That's because if a user types nin and then stops, he would try again to fix the typo by appending e to nin which will result again in two different objects with the following
[ { name: "text", value: 'nin' }]
[ { name: "text", value: 'nine' }]
While if you listen to onBlur event, you can just empty the input and ask user to add a new optional field. That way giving the user time to think and look in case of any typo
<input
type="text"
className="inputStyle"
placeholder="test"
onBlur={(e) =>
this.generateExtraFieldData(
e.target, // pass the e.target so we can empty the input after adding to array
'name'
)
}
/>
generateExtraFieldData = (target, type) => {
const { optionalFields } = this.state;
var joined = optionalFields.concat({ name: "text", value: target.value });
this.setState({
optionalFields: joined,
});
target.value = '';
};
Here's the working Plunker
I have the following JSON:
data: {
questions: "[{"id":"vzDDWL3GQvJi","title":"This is question 1","type":"opinion_scale","ref":"data_q1","properties":[]},{"id":"okT0ieWJm74d","title":"This is question 2","type":"opinion_scale","ref":"data_q2","properties":[]},
answers: "[{"type":"number","number":2,"field":{"id":"vzDDWL3GQvJi","type":"opinion_scale","ref":"data_q1"}},{"type":"number","number":4,"field":{"id":"okT0ieWJm74d","type":"opinion_scale","ref":"data_q2"}},
createdDate: "2020-02-14T07:43:02.000000Z"
}
A tidy version of the above is:
Question (questions object)
title : "This is question 1"
ref" : "data_q1"
Answer (answers object)
ref" : "data_q1"
number : 2
So, for question 1 (with the ref: data_q1) the number (score) is 2.
What I'm trying to do, is to merge both questions answers together based on ref. I want to do this so that I can get the number. I.e. question and answer for data_q1 is 2.
I have the following:
// Get questions
var questionData = data.data.questions;
var questions = JSON.parse(questionData);
// get answers
var answerData = data.data.answers;
var answers = JSON.parse(answerData);
What I've tried:
var answersInfo = answers.map( function(order) {
if( answers.ref === "RefIDHere"){
var info = { "number": answers.number}
return info;
}
});
console.log(answersInfo);
However, the issue with the above is, in answers.ref ===, I don't know what to pass because the questions and answers haven't been mapped together yet.
Please try below solution.
finalResult = [];
questions.map( que =>
answers.map(ans => {
if(que.ref === ans.field.ref){
finalResult.push({question: que, answer: ans})
}
return finalResult;
}));
Now if you see finalResult has the question and respective answer and you can access the number.
Hope it helps.
1) Build an answer_number object which has ref as key and value as number from data.answers.
2) Use map over data.questions and add the number value from above.
Hope this helps.
const data = {
questions: [
{
id: "vzDDWL3GQvJi",
title: "This is question 1",
type: "opinion_scale",
ref: "data_q1",
properties: []
},
{
id: "okT0ieWJm74d",
title: "This is question 2",
type: "opinion_scale",
ref: "data_q2",
properties: []
}
],
answers: [
{
type: "number",
number: 2,
field: { id: "vzDDWL3GQvJi", type: "opinion_scale", ref: "data_q1" }
},
{
type: "number",
number: 4,
field: { id: "okT0ieWJm74d", type: "opinion_scale", ref: "data_q2" }
}
],
createdDate: "2020-02-14T07:43:02.000000Z"
};
const answers_number = data.answers.reduce(
(acc, curr) => Object.assign(acc, { [curr.field.ref]: curr.number }),
{}
);
const questions_count = data.questions.map(que => ({
...que,
number: answers_number[que.ref]
}));
console.log(questions_count);
Use filter:
let data = {
questions: '[{"id":"vzDDWL3GQvJi","title":"This is question 1","type":"opinion_scale","ref":"data_q1","properties":[]},{"id":"okT0ieWJm74d","title":"This is question 2","type":"opinion_scale","ref":"data_q2","properties":[]}]',
answers: '[{"type":"number","number":2,"field":{"id":"vzDDWL3GQvJi","type":"opinion_scale","ref":"data_q1"}},{"type":"number","number":4,"field":{"id":"okT0ieWJm74d","type":"opinion_scale","ref":"data_q2"}}]',
createdDate: "2020-02-14T07:43:02.000000Z"
}
let getQuestionAndAnswer = (data, ref) => {
let question = JSON.parse(data.questions).filter(v => v.ref === ref);
let answer = JSON.parse(data.answers).filter(v => v.field.ref === ref);
return {
question,
answer
};
};
console.log(getQuestionAndAnswer(data, "data_q1"));
I am new to react world, I can't manage to change the state properly from form input field. I am building an employee profile that is going to be saved in a database. I created a profile in component state and get user data from the input field. But however, salary and headline fields are not changing while OnChange event handling function. Candidate is an object representation of employee
this.state = {
candidate: {
account: {
firstName: '',
lastName: '',
email: '',
phone: '',
},
salary: '',
headline: '',
topSkills: [{
experience1: '',
title1: ''
}, {
experience2: '',
title2: ''
}, {
experience3: '',
title3: ''
},
],
}
}
onChangefunction
handleChange(e) {
const name = e.target.name;
const value = e.target.value;
let copyState = Object.assign({},
this.state.candidate);
copyState.account[name] = value;
copyState.topSkills[name] = value;
copyState.salary = value;
copyState.headline = value;
this.setState(copyState);
}
The input field in salary and headline is not accepting input from user
<input
name="salary"
type="number"
value={this.state.candidate.salary|| ''}
onChange={this.handleChange}
/>
Can anyone provide me with help and suggest how to structure setState on onChange function?
You can simply handle changes like that for inputs:
state = {
candidate: {
account: {
firstName: '',
lastName: '',
email: '',
phone: '',
},
salary: '',
headline: '',
topSkills: [
{
experience1: '',
title1: ''
},
{
experience2: '',
title2: ''
},
{
experience3: '',
title3: ''
},
],
}
}
handleChange = (e) => {
this.setState( { candidate: { [e.target.name]: e.target.value }})
}
SetState does not required the entire object just what you are updating in the state.
Based on what you already have you could just do this
handleChange(e) {
const name = e.target.name;
const value = e.target.value;
this.setState({
account[name]: value,
topSkills[name]: value,
salary: value,
headline: value,
});
}
Though looking at your implementation, I'm not sure you will achieve what you want here... It looks like if you updated Salary, you account[name], topSkills[name], and 'headline` would be updated to the value you entered for salary.
As devserkan mentioned you can update one field at a time with setState
so what you could do is...
<input
name="salary"
type="number"
value={this.state.candidate.salary|| ''}
onChange={(e)=>this.setState({ salary: e.currentTarget.value })}/>
This is slightly inefficient because it would recreate the onChange function on every render. Your approach of creating a function outside the render in this case better...
handleSalaryChange { (e)=>this.setState({ salaray: e.currentTarget.value }); }
handleHeadlineChange { (e)=>this.setState({ headline: e.currentTarget.value }); }
render{ return (
<div>
<input
name="salary"
type="number"
value={this.state.candidate.salary|| ''}
onChange={this.handleSalaryChange)}/>
<input
name="headline"
value={this.state.candidate.headline|| ''}
onChange={this.handleHeadlineChange)}/>
...
</div>
)}
UPDATE For the handle*Change functions to work as they are currently, state would need to be updated to remove the candidate wrapper....
state = {
account: {
firstName: '',
lastName: '',
email: '',
phone: '',
},
salary: '',
headline: '',
topSkills: [
{
experience1: '',
title1: ''
},
{
experience2: '',
title2: ''
},
{
experience3: '',
title3: ''
},
],
}
Credit to udemy academy MLR — Teaching Assistant. He solved this way,the answer solve the problem.
handleChange = e => {
const candidateClone = Object.assign({}, this.state.candidate);// Shallow clone.
const accountClone = Object.assign({}, this.state.candidate.account);// Deep clone.
const topSkillsClone = Object.assign({}, this.state.candidate.topSkills);// Deep clone.
// below (let): Persists the last entered value (required).
let myHeadline = candidateClone.headline;
let myFirstName = candidateClone.account.firstName;
let mySalary = candidateClone.salary;
let myTopSkillsTitle = candidateClone.topSkills[0].title;
switch (e.target.name) {
case "headlineInput": // name in input field
myHeadline = e.target.value;
break;
case "firstNameInput": // name in input field
myFirstName = e.target.value;
break;
case "salaryInput":
mySalary = e.target.value;
break;
case "topSkillsTitleInput": // name in input field
myTopSkillsTitle = e.target.value;
break;
default:
console.log("Switch statement error");
}
accountClone.firstName = myFirstName;// Place the property value inside the deep cloned embedded object.
topSkillsClone[0].title = myTopSkillsTitle;// Place the property value inside the deep cloned embedded array.
candidateClone["account"] = accountClone;// Place the deep cloned embedded object inside the shallow cloned main object.
candidateClone["salary"] = mySalary;// Place the property inside the shallow cloned main object.
candidateClone["headline"] = myHeadline;// Place the property inside the shallow cloned main object.
candidateClone["topSkills"] = topSkillsClone;// Place the deep cloned embedded array inside the shallow cloned main object.
this.setState({candidate:candidateClone});
};
so I'm developing a quiz application sort of, so this is my initial state of the app when it first launch, I also have quizApplication.js component store all the question and answers,
{
question: "I am task oriented in order to achieve certain goals",
answers: [
{
type: "Brown,D,JP",
content: "Hell Ya!"
},
{
type: " ",
content: "Nah"
}
]
},
and here is my function to set the user answer
setUserAnswer(answer) {
if (answer.trim()) {
const answer_array = answer.split(',');
const updatedAnswersCount = update(this.state.answersCount, {
[answer]: {$apply: (currentValue) => currentValue + 1},
});
this.setState({
answersCount: updatedAnswersCount,
answer: answer
});
}
}
I also have a AnswerOption component like so
function AnswerOption(props) {
return (
<AnswerOptionLi>
<Input
checked={props.answerType === props.answer}
id={props.answerType}
value={props.answerType}
disabled={props.answer}
onChange={props.onAnswerSelected}
/>
<Label className="radioCustomLabel" htmlFor={props.answerType}>
{props.answerContent}
</Label>
</AnswerOptionLi>
);
}
So what im try to do is that whenever the user click on HellYa! it will increment "Brown" and "D" and "JP" by +1, but right now it gives me a new answersCount value as Brown,D,JP: null, so how should I achieve this? Many thanks!
You have split your type, but havent made use of them yet.
As you have split your type, you would get answer_array with a length of 3 containing ["Brown", "D", "JP"]
const answer_array = answer.split(',');
Next you are updating your state with the updated answer count. You are performing the below
const updatedAnswersCount = update(this.state.answersCount, {
[answer]: {$apply: (currentValue) => currentValue + 1},
});
Here answer contains "Brown,D,JP". Since you want to update each of it by +1, lets loop over the split value and update it.
let updatedAnswersCount = null;
answer_array.forEach((key) => {
updatedAnswersCount = update(this.state.answersCount, {
[answer]: {$apply: (currentValue) => currentValue + 1},
});
}
Here, i'm assuming that your type is unique. Meaning Brown/D/JP is present only for this answer and not for anything else. So we are assuming all will have same value.