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.
Related
If you have an array as part of your state, and that array contains objects, whats an easy way to update the state with a change to one of those objects?
Example, modified from the tutorial on react:
var CommentBox = React.createClass({
getInitialState: function() {
return {data: [
{ id: 1, author: "john", text: "foo" },
{ id: 2, author: "bob", text: "bar" }
]};
},
handleCommentEdit: function(id, text) {
var existingComment = this.state.data.filter({ function(c) { c.id == id; }).first();
var updatedComments = ??; // not sure how to do this
this.setState({data: updatedComments});
}
}
I quite like doing this with Object.assign rather than the immutability helpers.
handleCommentEdit: function(id, text) {
this.setState({
data: this.state.data.map(el => (el.id === id ? Object.assign({}, el, { text }) : el))
});
}
I just think this is much more succinct than splice and doesn't require knowing an index or explicitly handling the not found case.
If you are feeling all ES2018, you can also do this with spread instead of Object.assign
this.setState({
data: this.state.data.map(el => (el.id === id ? {...el, text} : el))
});
While updating state the key part is to treat it as if it is immutable. Any solution would work fine if you can guarantee it.
Here is my solution using immutability-helper:
jsFiddle:
var update = require('immutability-helper');
handleCommentEdit: function(id, text) {
var data = this.state.data;
var commentIndex = data.findIndex(function(c) {
return c.id == id;
});
var updatedComment = update(data[commentIndex], {text: {$set: text}});
var newData = update(data, {
$splice: [[commentIndex, 1, updatedComment]]
});
this.setState({data: newData});
},
Following questions about state arrays may also help:
Correct modification of state arrays in ReactJS
what is the preferred way to mutate a React state?
I'm trying to explain better how to do this AND what's going on.
First, find the index of the element you're replacing in the state array.
Second, update the element at that index
Third, call setState with the new collection
import update from 'immutability-helper';
// this.state = { employees: [{id: 1, name: 'Obama'}, {id: 2, name: 'Trump'}] }
updateEmployee(employee) {
const index = this.state.employees.findIndex((emp) => emp.id === employee.id);
const updatedEmployees = update(this.state.employees, {$splice: [[index, 1, employee]]}); // array.splice(start, deleteCount, item1)
this.setState({employees: updatedEmployees});
}
Edit: there's a much better way to do this w/o a 3rd party library
const index = this.state.employees.findIndex(emp => emp.id === employee.id);
employees = [...this.state.employees]; // important to create a copy, otherwise you'll modify state outside of setState call
employees[index] = employee;
this.setState({employees});
You can do this with multiple way, I am going to show you that I mostly used. When I am working with arrays in react usually I pass a custom attribute with current index value, in the example below I have passed data-index attribute, data- is html 5 convention.
Ex:
//handleChange method.
handleChange(e){
const {name, value} = e,
index = e.target.getAttribute('data-index'), //custom attribute value
updatedObj = Object.assign({}, this.state.arr[i],{[name]: value});
//update state value.
this.setState({
arr: [
...this.state.arr.slice(0, index),
updatedObj,
...this.state.arr.slice(index + 1)
]
})
}
This question already has answers here:
What is the most efficient way to deep clone an object in JavaScript?
(67 answers)
Closed 1 year ago.
I have an array like :
export const switches = [
{ id: 1, switchName: 'HS - 3015', operation: 'Auto Start GA-3001 S', isActive: true },
{ id: 2, switchName: 'HS - 3016', operation: 'FSLL - 3001 (Pass - 1)', isActive: false },
{ id: 3, switchName: 'HS - 3017', operation: 'FSLL - 3002 (Pass - 2)', isActive: true }
];
In my component I added a additional property value which is boolean and which is used for control switch.
In a function I first duplicate this array and than modify this duplicate array that is value: true/false to value: 'normal/bypass'. The big issue is I am not getting my original array. I am always get the modified array.
What is the problem, I can't figure out.
Switch change and Submit function like this:
const onSwitchChange = (e, switchId) => {
const { checked } = e.target;
const oldState = [...state];
const updatedState = oldState.map(s => {
if (s.id === switchId) {
s['value'] = checked;
}
return s;
});
setState(updatedState);
};
const onSubmit = () => {
const oldData = [...state];
const data = oldData.map(item => {
item.value = item.value === true ? 'Bypass' : 'Normal';
return item;
});
document.getElementById('jsonData').textContent = JSON.stringify(data, null, 2);
};
Find in codesandbox: https://codesandbox.io/s/switch-control-4ovyk
the map function will always return a new array with elements that are the result of a callback function.
In your case, the map function should return a new array with the value changed to Bypass or Normal and it will be stored in the constant data.
However, you can still access your original array by calling oldData
my 'handleGrade' function is giving an error when I try to update the grade value when received.
function Sgpa() {
const [subjects, setSubjects] = useState([
{
name: "subject name",
credits: 3,
points: 0,
grade: "B+"
}
]);
const [sgpa, setSgpa] = useState(0);
function handleAdd() {
setSubjects((prevValues) => [
...prevValues,
{
name: "subject name",
credits: 3,
points: 3,
grade: "B+"
}
]);
}
useEffect(() => {
calcSgpa();
});
function calcSgpa() {
let totalCredits = 0;
let totalPoints = 0;
subjects.map((subject, i) => {
totalCredits += subject.credits;
totalPoints += subject.points;
});
setSgpa((totalCredits * totalPoints) / totalCredits);
}
The error is down below. I'm receiving the correct value from event.target and I think I'm failing to update the value inside my array of objects.
function handleGrade(event, i) {
console.log(event.target.value);
setSubjects(...subjects,{ ...subjects[i] , grade:event.target.value });
console.log(subjects);
}
return (
<>
<h3>Sgpa : {sgpa}</h3>
{subjects.map((subject, i) => {
return (
<SgpaComponent subject={subject} key={i} handleGrade={handleGrade} />
);
})}
<button onClick={handleAdd}>+</button>
</>
);
}
map error happens because,
setSubjects(...subjects,{ ...subjects[i] , grade:event.target.value });
Here, you are setting objects instead of array of objects. "..." will pull everything out of array and you forgot to create a new array while pulling everything out. After that you are trying to map the subjects, where it is not an array anymore.
You can do this is in several ways. One of the best way is instead of changing old state directly, you can copy the old state and update that. Finally return the new updated state. Every setState in react will accept the function, which will give you the old state value.
Code will look like this after changes
function handleGrade(event, i) {
setSubjects(oldSubjects => {
let newSubjects = [...oldSubjects];
newSubjects[i] = {
...newSubjects[i],
grade: event.target.value
};
return newSubjects;
});
}
Hope it answered your question.
One data set is an object of arrays of ids and another is an object of arrays of ids and names. What I'd like to do is check if the ids from the first data exist in the second data set and if they do then display the names.
This is what is being called by the component, which works correctly:
<td>Genre</td>
<td>{this.matchGenres(this.props.movie.genre_ids, this.props.genres)}</td>
And this is the function that I can't get to work:
matchGenres = (genres, genreList) => {
genres.forEach((genre) => {
genreList.filter((list) => {
return list.id === genre;
}).map((newList) => {
return newList.name;
});
});
}
It looks like the operation performs correctly and returns the right names when I console.log it! But! its not showing up in the component on render.
const genres = [{
id: 1,
name: "Jazz Music"
}, {
id: 2,
name: "Something"
}];
const genreList = [1, 10, 100];
matchGenres = (genres, genreList) => genres
.filter(genre => genreList.includes(genre.id))
.map(genre => genre.name);
const matchedGenres = matchGenres(genres, genreList);
console.log(matchedGenres);
But! its not showing up in the component on render.
Its because your function doesn't return anything. You return inside filter and map and your function does not return anything. Also note that forEach always return undefined
You just need a minor change. Try this
let genres = ["1", "2", "3"];
let genreList = [{
id: "2",
name: "Two"
}, {
id: "32",
name: "Three"
}]
matchGenres = (genres, genreList) => {
return genreList.filter((list) => {
// findIndex return array index if found else return -1 if not found
return genres.findIndex(genere => genere === list.id) > -1;
}).map(list => list.name);
}
console.log(matchGenres(genres, genreList));
This is the solution that ended up working:
if (genreList.length !== 0) {
return genres.map(genre => genreList.find(list => list.id === genre)).map((newList) => newList.name) + ',';
}
For some reason the value of GenreList, which is an array, was showing up as empty for the first couple times the function is call. Thats another problem I'll have to look at but the if statement solves for it for the time being.
There is a react questionnaire component which add selected answers to this.state = { answer: []} which works fine. At the same time when the user update answer it add as another object. Is there any option to update the object in setState when the questionID is the same ?
onChange(event) {
const field = event.target.name;
let question = event.target.dataset.questionId;
let result = event.target.value;
let answer = {
'questionID': question,
'answerValues': result
};
this.setState({
answer: [
...this.state.answer,
answer,
]
});
console.log(this.state.answer);
}
Currently same question adding like this
[
{
"questionID": 1,
"answerValues": 2
},
{
"questionID": 2,
"answerValues": 5
}
{
"questionID": 1,
"answerValues": 1
}
]
any solution to update the object if already exist same questionID is this.state.answer ?
You can reduce like that :
this.setState({
answer: this.state.answer.reduce((acc,ans)=>{
return ans.questionID === answer.questionID
? [...acc,answer]
: [...acc,ans]
},[])
})
You can do it like with array map
this.setState({
answer: this.state.answer.map(
(existingAnswer) => (existingAnswer.questionID === answer.questionID
? answer : existingAnswer)),
});
You need to use object assign, in this code, you will be able to update the question if it already exists by ID else it will be adding a new question
const field = event.target.name;
let question = event.target.dataset.questionId;
let result = event.target.value;
let newAnswer = {
'questionID': question,
'answerValues': result
};
this.setState(prevState => {
const user = prevState.answer.find(d => d.questionID === newAnswer.questionID);
if (!!user) {
Object.assign(user, newAnswer);
const data = prevState.answer.map(d => d.questionID === user.questionID ? user : d);
return { answer: data }
} else {
return { answer: prevState.answer.concat(newAnswer)};
}
})
like Martin said use
let questionExistsInState = (this.state.answer.some((ans) => (ans.questionId === answer.questionId)));
let newState = []
if(questionExistsInState) {
newState = this.state.answer.map((ans) => (ans.questionId === answer.questionId ? answer : ans))
} else {
newState = [answer, ...this.state.answer]
}
this.setState({
answer: newState ,
});
Also I see that you are using setState in an onChange event. Please note that setState is not a synchronous operation, it is not guarenteed that you will see the new state if you are console logging immediately after setState() call. You can console log in the callback(second argument) of setState function.
I suggest storing answers as an object where the key is the question id and the value is the answer. In your constructor:
this.state = { answer: {}}
Then in onChange():
this.setState({
answer: Object.assign({}, this.state.answer, {
[question]: result,
});
});