I am brand new to React and I thought I was doing this correctly. I am trying to keep score for a trivia game I'm developing. When I set state, the score is updating but the number of correct responses is not. Interestingly, this is only the case when I deploy the site to firebase, it works as expected when served locally.
This function is in the parent game component. this.state.points is incrementing, this.state.numberCorrect/Incorrect is not.
childSubmitAnswer(dataFromChild) {
if (dataFromChild.correct === true) {
this.setState((prevState) => {
return {
numberCorrect: prevState.numberCorrect++,
points: prevState.points + dataFromChild.points
}
});
} else {
this.setState((prevState) => {
return {
numberIncorrect: prevState.numberIncorrect++,
points: prevState.points - dataFromChild.points
}
});
}
console.log(this.state.numberCorrect);
}
This is the function called in the child component which is displaying the question.
handleSubmit(event) {
event.preventDefault();
let verification = new AnswerVerification();
this.answerSubmitted();
if (verification.verifyAnswer(this.state.submissionValue, this.state.answer)) {
let data = {
correct: true,
points: this.props.points
}
this.props.childSubmitAnswer(data);
} else {
let data = {
correct: false,
points: this.props.points
}
this.props.childSubmitAnswer(data);
}
}
Ciao, I think the problem is on childSubmitAnswer function. Try to modify it like that:
childSubmitAnswer(dataFromChild) {
if (dataFromChild.correct === true) {
this.setState((prevState) => {
return {
numberCorrect: prevState.numberCorrect + 1,
points: prevState.points + dataFromChild.points
}
});
} else {
this.setState((prevState) => {
return {
numberIncorrect: prevState.numberIncorrect + 1,
points: prevState.points - dataFromChild.points
}
});
}
console.log(this.state.numberCorrect);
}
Explanation: in Javascript when you use ++ after the operand, the value will be returned before the operand is increased. Example:
let a = 0;
console.log(a++); // shows 0
console.log(a); // shows 1
Related
Can someone explain, why I'm getting an error, when I'm removing items from an array? It works once, but then it crashes. Checked - is boolean meaning.
removeCards = () => {
console.clear();
for (let i = 0; i < this.state.cards.length; i++) {
console.log(this.state.cards[i]);
if (this.state.cards[i].checked) {
delete this.state.cards[i];
}
}
this.setState({ cards: this.state.cards });
};
In React, state is immutable. So instead of trying to alter it directly, create a copy of it and then apply that to state -
removeCards = () => {
console.clear();
const newCards = [];
for (let i = 0; i < this.state.cards; i++) {
if (!this.state.cards[i].checked) {
newCards.push(this.state.cards[i];
}
}
this.setState({ cards: newCards });
};
Maybe my trouble in that? That's how i'm chaning states from true to false and in reverse.
myFunc = (props) => {
let num = Number(props);
num--;
let cards = [...this.state.cards];
if (this.state.cards[num].checked) {
cards[num] = { ...cards[num], checked: false };
} else {
cards[num] = { ...cards[num], checked: true };
}
this.setState({ cards });
};
Following is the piece of code which is working fine, but I have one doubt regarding - const _detail = detail; code inside a map method. Here you can see that I am iterating over an array and modifying the object and then setting it to setState().
Code Block -
checkInvoiceData = (isUploaded, data) => {
if (isUploaded) {
const { invoiceData } = this.state;
invoiceData.map(invoiceItem => {
if (invoiceItem.number === data.savedNumber) {
invoiceItem.details.map(detail => {
const _detail = detail;
if (_detail.tagNumber === data.tagNumber) {
_detail.id = data.id;
}
return _detail;
});
}
return invoiceItem;
});
state.invoiceData = invoiceData;
}
this.setState(state);
};
Is this approach ok in React world or I should do something like -
const modifiedInvoiceData = invoiceData.map(invoiceItem => {
......
code
......
})
this.setState({invoiceData: modifiedInvoiceData});
What is the pros and cons of each and which scenario do I need to keep in mind while taking either of one approach ?
You cannot mutate state, instead you can do something like this:
checkInvoiceData = (isUploaded, data) => {
if (isUploaded) {
this.setState({
invoiceData: this.state.invoiceData.map(
(invoiceItem) => {
if (invoiceItem.number === data.savedNumber) {
invoiceItem.details.map(
(detail) =>
detail.tagNumber === data.tagNumber
? { ...detail, id: data.id } //copy detail and set id on copy
: detail //no change, return detail
);
}
return invoiceItem;
}
),
});
}
};
Perhaps try something like this:
checkInvoiceData = (isUploaded, data) => {
// Return early
if (!isUploaded) return
const { invoiceData } = this.state;
const updatedInvoices = invoiceData.map(invoiceItem => {
if (invoiceItem.number !== data.savedNumber) return invoiceItem
const details = invoiceItem.details.map(detail => {
if (detail.tagNumber !== data.tagNumber) return detail
return { ...detail, id: data.id };
});
return { ...invoiceItem, details };
});
this.setState({ invoiceData: updatedInvoices });
};
First, I would suggest returning early rather than nesting conditionals.
Second, make sure you're not mutating state directly (eg no this.state = state).
Third, pass the part of state you want to mutate, not the whole state object, to setState.
Fourth, return a new instance of the object so the object reference updates so React can detect the change of values.
I'm not saying this is the best way to do what you want, but it should point you in a better direction.
I have an activity feed, it contains a number of different types of activity for our site.
one type of activity is checkin. which logs when a user checks in and checkouts of a site.
The record entries look like so
Entryable_id | Entry_type | Action | timestamp
1 Ticket Update 12:01
3 Ticket New 12:07
4 CheckIn ClockedIn 14:30
4 CheckIn ClockedOut 17:30
What I want to do is create an array with entries in it like so
Entryable_id | ClockedIn| ClockedOut
4 14:30 17:30
so far what I have is
{
let staffCheckins = []
let checkinRecord = []
if (this.DiaryStore.entries.length) {
this.DiaryStore.entries.forEach(function(val) {
if (val.entryable_type === 'CheckIn') {
staffCheckins.push(val);
return val
}
})
}
staffCheckins.forEach(function(val) {
if (val.action === "ClockedIn") {
checkinRecord[val.entryable_id] = {
clockedIn: val.created_at,
user: val.user
}
}
if (val.action === "ClockedOut") {
checkinRecord[val.entryable_id] = {
clockedOut: val.created_at
}
}
})
console.log(completeCheckin)
},
which gives
1: clockedIn: "2019-07-22T10:26:45.000000Z",
2: clockedIn: "2019-07-22T12:38:02.000000Z"
so I assume that it is not appending to the key when i do
checkinRecord[val.entryable_id] = {clockedOut: val.created_at}
On top of that this all feels like a mess. is there a better way to filter and get what I need?
Thanks
You need to merge attribute, instead of assign to new object
staffCheckins.forEach(function(val) {
if (!checkinRecord[val.entryable_id]) {
checkinRecord[val.entryable_id] = {}
}
if (val.action === "ClockedIn") {
checkinRecord[val.entryable_id] = {
...checkinRecord[val.entryable_id],
clockedIn: val.created_at,
user: val.user
}
} else (val.action === "ClockedOut") {
checkinRecord[val.entryable_id] = {
...checkinRecord[val.entryable_id],
clockedOut: val.created_at
}
}
}
so I haven't gotten to test it because I'm out and about but you could try something like this. If they object entryable_id doesnt exist in the current object in the array, then it will create a new object with the members, otherwise it will find the object and update the fields
{
let staffCheckins = [];
let checkinRecord = [];
if (this.DiaryStore.entries.length) {
staffCheckins = this.DiaryStore.filter(val => val.entryable_type.toLowerCase() === 'checkin');
}
staffCheckins.forEach(function(val, i) {
let { action, entryable_id, created_at, user } = val;
if (!entryable_id in checkinRecord[i]) {
checkinRecord[i] = {
clockedIn: created_at,
clockedOut: created_at,
user
}
}
if (action.toLowerCase() === 'clockedin') {
checkinRecord[i] = {
...checkinRecord[i],
clockedIn: created_at,
user
}
} else if (action.toLowerCase() === 'clockedout') {
checkinRecord[i] = {
...checkinRecord[i],
clockedOut: created_at
}
}
});
}
apologies if I understood wrong but I'm also no currently at my actual computer to test any of it
You could do this whole operation in a filter reduce combination and create a groupBy object using the Entryable_id as keys.
Once loop completes get values array of that object
const checkinGroup = data.filter(({Entry_type}) => Entry_type === 'CheckIn')
.reduce((a, c)=>{
let {Entryable_id:id, Action, timestamp} = c;
a[id] = a[id] || {Entryable_id: id, ClockedIn: null, ClockedOut: null};
a[id][Action] = timestamp;
return a;
},{});
const res = Object.values(checkinGroup)
console.log(res)
<script>
const data = [{
Entryable_id: 1,
Entry_type: 'Ticket',
Action: 'Update',
timestamp: '12:01'
},
{
Entryable_id: 3,
Entry_type: 'Ticket',
Action: 'New',
timestamp: '12:07'
},
{
Entryable_id: 4,
Entry_type: 'CheckIn',
Action: 'ClockedIn',
timestamp: '14:30'
},
{
Entryable_id: 4,
Entry_type: 'CheckIn',
Action: 'ClockedOut',
timestamp: '17:30'
}
]
</script>
I have a mock userId which should be saved inside the users object of the reactions object when a certain icon is clicked inside my react component.
Below is a function updateUploadReaction that is supposed to do that for me. The logic is this, when an icon is clicked and this particular userId does not exist in the users object, it sets it inside the user object and adds 1, on clicking again it sets it to false and subtracts 1. So far, this is what I have, but it simply keeps subtracting 3 each time I click. I need a guide on exactly how to do that.
Here's a link to the full App. updateUploadReaction is inside components/home/reducers.js
reaction object
{
reactions: {
dislike: {
count: 0,
users: {},
},
like: {
count: 0,
users: {},
},
maybe: {
count: 0,
users: {},
},
},
}
function
function updateUploadReaction(id, type, uploads) {
const updatedUploads = new Map([...uploads.entries()]);
const upload = updatedUploads.get(id);
const userId = uuid();
uploads.forEach(() => {
if (!userId {
upload.reactions[type].count += 1;
upload.reactions[type]..users[userId] = true;
} else {
upload.reactions[type].count -= 1;
upload.reactions[type].users[userId] = false;
}
});
updatedUploads.set(id, upload);
return updatedUploads;
}
I think you might be looking for something like this, I'm not sure if you want to add a new userId and remove it, or do something else. Perhaps it is an array of userIds? I think this might help you get on the right track though:
const upload1 = {
reactions: {
dislike: {
count: 0,
users: { userId: 1 },
},
},
}
const upload2 = {
reactions: {
dislike: {
count: 0,
users: {},
},
},
}
const uploads = [ upload1, upload2 ];
const updateObjectReaction = ( id, type, uploads ) => {
uploads.forEach( upload => {
const { users } = upload.reactions[ type ]
if ( Object.values( users ).includes( id ) ) {
delete users.userId
}
else {
users.userId = id;
}
} );
console.log( { upload1Users: uploads[ 0 ].reactions.dislike.users } )
console.log( { upload2Users: uploads[ 1 ].reactions.dislike.users } )
}
updateObjectReaction( 1, "dislike", uploads )
I have problem doing a setState changing value of a nested array of object. Below code suppose
to change question of id 2 to answer: true but it did not, what's wrong?
this.state = {
questions: [
{
id: 1,
answer: ''
},
{
id: 2,
answer: ''
},
]
}
//I have a click event somewhere
this.setState(
{
questions: this.state.questions.map(q => {
if (q.id === 2) {
return {
...q,
answer: true
}
} else {
return { ...q }
}
})
},
console.log(this.state.questions[1]) // did not see id of 2 being changed to true?
)
The console.log(this.state.questions[1]) line is executed before the this.setState line is executed, that's why the old state is printed to the console. You should put the line inside a function to delay the execution:
this.setState(..., () => console.log(this.state.questions[1]));
Also it is recommended to use a function as the first argument if the changed state is derived from the current state because React doesn't apply the new state immediately therefore this.state can be outdated when React applies the new state:
this.setState(state => ({
questions: state.questions.map(q => {
if (q.id === 2) {
return {...q, answer: true};
}
return q;
})
}), () => {
console.log(this.state.questions[1]);
});
You are not invoking your setState callback. Try like this:
this.setState(
{
questions: this.state.questions.map(q => {
if (q.id === 2) {
return {
...q,
answer: true
};
}
return { ...q };
})
},
() => console.log(this.state.questions[1]) // did not see id of 2 being changed to true?
);
Though, since you are using the current state to update your state again, it would be better to use functional setState.
this.setState(
currentState => ({
questions: currentState.questions.map(q => {
if (q.id === 2) {
return {
...q,
answer: true
};
}
return { ...q };
})
}),
() => console.log(this.state.questions[1])
);
Also, you don't have to log your state in a callback to setState. You can log your state in your render method without struggling setState's callback.
this.setState(
currentState => ({
questions: currentState.questions.map(q => {
if (q.id === 2) {
return {
...q,
answer: true
};
}
return { ...q };
})
})
);
....
render() {
console.log( this.state );
....
}
I think it's because Array.map returns an array. Try:
this.setState(
{
questions: this.state.questions.map(q => {
if (q.id === 2) {
q.answer = true;
}
return q;
})
},
console.log(this.state.questions[1]) // did not see id of 2 being changed to true?
)