Add to a players total score - javascript

When a player scores, I want to add +1 to the players total score (totPoints).
Unfortunately, my present code:
const [number, setNumber] = useState('');
const [totPoints, setTotPoints] = useState(0);
const [scorers, setScorers] = useState([]);
const sortedScorers = [...scorers].sort((a, b) => a.number - b.number);
const onePointScoredHandler = () => {
const players = [...scorers];
const pos = players.map((player) => player.number).indexOf(+number);
if (pos !== -1) {
console.log('exist');
players[pos].totPoints = setTotPoints(totPoints + 1);
setScorers(players);
} else {
console.log('new');
const newScorer = {
id: nanoid(4),
number: +number,
totPoints: totPoints + 1,
};
setScorers([...scorers, newScorer]);
setTotPoints(totPoints);
}
setNumber('');
console.log(scorers);
};
return (
<div className="App">
<h4>Individual points</h4>
<br />
<br />
<br />
<input
type="number"
value={number}
onChange={(e) => setNumber(e.target.value)}
/>
<br />
<br />
<button onClick={onePointScoredHandler}>Add 1p</button>
<br />
<br />
<table className="table">
<thead>
<tr>
<th>ID</th>
<th>NUMBER</th>
<th>Total Points</th>
</tr>
</thead>
<tbody>
{sortedScorers
.sort((a, b) => a.number - b.number)
.map((player) => (
<tr key={player.id}>
<td>{player.id}</td>
<td>{player.number}</td>
<td>{player.totPoints}</td>
</tr>
))}
</tbody>
</table>
</div>
);
};
don't works as intended:
When the player scores first time, it's working, i.e totPoints = 1
if that player scores again i get totPoints: undefined
If now a new player scores, the totPoints = 2 and nottotPoints = 1 as it should be
I hope you can rescue me even this time....
Thanks in advance
Regards
Peter
PS: I´ve tried with
players[pos].totPoints = setTotPoints((prevState) => prevState + 1)
with the same disappointing results...

Issue
You are mutating the state object.
players[pos].totPoints = setTotPoints(totPoints + 1); // mutation!!
setScorers(players);
Even though you copied the array with const players = [...scorers]; the elements in the array still reference the elements of the old array.
Solution
Use a functional state update to match a previous scorer to increment their totPoints value. Remember, when updated React state you must shallow copy any state that is being updated, included nested state.
setScorers((scorers) =>
scorers.map((scorer, index) =>
index === pos
? {
...scorer,
totPoints: scorer.totPoints + 1
}
: scorer
)
);

Related

Reset button is not working on react hooks

I have a number guessing game in React, the reset button is not clearing the fields.
This is my code:
import React, { useState } from "react";
import Button from "react-bootstrap/Button";
import Form from "react-bootstrap/Form";
let luckyNumber = Math.floor(Math.random() * 100) + 1;
function GuessingGame() {
const [guess, setGuess] = useState("");
const [count, setCount] = useState(0);
const [guesses, setGuesses] = useState([]);
const [luckyN, setLuckyN] = useState(luckyNumber);
const [message, setMessage] = useState(" Start guessing...");
const [disabled, setDisabled] = useState(false);
const handlerValueChange = (event) => {
setGuess(event.target.value);
};
const submitHandler = () => {
if (+luckyNumber === +guess) {
setMessage(" Congratulations! You guessed the Lucky Number!.");
setDisabled(true);
} else if (count === 2) {
setMessage(" Game over, maximum guess attempts have been reached.");
setDisabled(true);
} else if (luckyNumber < guess) {
setMessage(" Guess is too high.");
} else if (luckyNumber > guess) {
setMessage(" Guess is too low.");
}
setCount(count + 1);
setGuesses([...guesses, guess]);
};
const resetBtn = () => {
setDisabled(false);
setGuess = 0;
setCount = 0;
guess = ('')
message = "";
setGuess = ([])
setLuckyN = (0);
setMessage = (' Start guessing...');
setLuckyN(Math.floor(Math.random()* 100) + 1)
};
return (
<>
<h4>
I am thinking of a number between 1 and 100. Guess the Lucky Number!
</h4>
<p>You have made {count} guesses </p>
<p>Your guess is too high! Your guess is too low!</p>
<p> Your current guess is {guess}</p>
<Form>
<Form.Control
disabled={disabled}
type="text"
value={guess}
onChange={handlerValueChange}
></Form.Control>
<Button disabled={disabled} onClick={submitHandler}>
Guess
</Button>
<br />
<br />
<Button onClick={resetBtn}>Reset</Button>
</Form>
<p>message:{message}</p>
<p>Total plays: {count}</p>
<p>You are trying to guess this lucky number: {luckyNumber}!</p>
<p>Your guesses: </p>
{guesses?.map((item, index) => {
return <span key={index}>{item} </span>;
})}
</>
);
}
export default GuessingGame;
The button needs to clear the fields, load the random number and start again, it is not doing that and even after the validation of 3 incorrect attempts, if I hit reset and hit guess again, the count keeps going and the program runs like if there is no validation...
Any helps with this? Thanks!!
Call the setStates as a function, not as an assignment in your resetBtn function.
For example:
const resetBtn = () => {
setDisabled(false);
setCount(0);
setGuess(0);
setMessage('');
setGuess([])
setLuckyN(0);
setMessage(' Start guessing...');
setLuckyN(Math.floor(Math.random()* 100) + 1);
};

How can I concatenate index to a function in Reactjs elements

I have many methods with numbering and i want to make a single render for input element using loop.
Here is my code:
{new Array(6).fill(0).map((inp, index) =>(
<input
key = {index}
className="input_otp"
theme={{ color }}
ref = {inputRefs[index]}
onChange={handleChange}
onKeyDown={onKeyOTP2}
onFocus={onFocusOTP2}
type="tel"
maxlength="1"
value={state['otp'+ (index + 1)]}
name={'otp'+ (index + 1)}
/>
))
}
Now I want to add index to these event methods to make it dynamic onKeyOTP+(index+1) becomes
onKeyDown={onKeyOTP2}
Just like i did with other properties. Need help regarding this.
You can bind the function with this.
{new Array(6).fill(0).map((inp, index)=>(
<input
key = {index}
className="input_otp"
theme={{ color }}
ref = {inputRefs[index]}
onChange={handleChange}
onKeyDown={this[`onKeyOTP${index + 1}`]}
onFocus={onFocusOTP2}
type="tel"
maxlength="1"
value={state['otp'+ (index + 1)]}
name={'otp'+ (index + 1)}
/>
))
}
this.abc1 = () => {
return '1';
}
this.abc2 = () => {
return '2'
}
for(let i = 1; i< 3; i++){
let j = this[`abc${i}`];
console.log("j is", j());
}
something like this

How do I round a React component property value to 2 decimal places?

I tried using {val}.toFixed(2) but since I have to return that value all it does is return the value and the string ".toFixed(2)". I also tried {val.toFixed(2)} but it throws an error because "val.toFixed(2)" is not a function. It's located at the Statistic Component
(I'm currently studying FullStack Open from the University of Helsinki)
How do I do this?
import React, { useState } from 'react'
import ReactDOM from 'react-dom'
const Button = ({ onClick, text }) => (
<button onClick={onClick}>
{text}
</button>
)
const Statistic = ({text, val}) => {
return (
<table>
<tbody>
<tr>
<td>{text}</td>
<td> {val}</td>
</tr>
</tbody>
</table>
)
}
const Statistics = ({total, good, neutral, bad}) => {
return (
<div>
<Statistic text = "good" val = {good} />
<Statistic text = "neutral" val = {neutral} />
<Statistic text = "bad" val = {bad} />
<Statistic text = "total" val = {total} />
<Statistic text = "average" val = {(good - bad) / total} />
<Statistic text = "positive" val = {good / total + "%"} />
</div>
)
}
const Feedback = ({total, good, neutral, bad}) => {
if (total === 0) {
return (
<div> No feedback given. </div>
)
}
return (
<Statistics total = {total} good = {good} bad = {bad} neutral = {neutral}/>
)
}
const App = () => {
const [good, setGood] = useState(0)
const [neutral, setNeutral] = useState(0)
const [bad, setBad] = useState(0)
const [total, setTotal] = useState(0)
const addGood = () => {
setGood(good + 1)
setTotal(total + 1)
}
const addBad = () => {
setBad(bad + 1)
setTotal(total + 1)
}
const addNeutral = () => {
setNeutral(neutral + 1)
setTotal(total + 1)
}
return (
<div>
<h1>Give your Feedback!</h1>
<Button onClick={addGood} text="Good!" />
<Button onClick={addNeutral} text="Neutral" />
<Button onClick={addBad} text="Bad" />
<h1>Statistics</h1>
<Feedback total = {total} good = {good} bad = {bad} neutral = {neutral} />
</div>
)
}
ReactDOM.render(<App />,
document.getElementById('root')
)
Thank you!
You should pass the val as prop in whatever format from the Statistics component itself. The Statistic component should just be a dumb component. In this way you don't need to handle the type even.
Try:
const Statistics = ({total, good, neutral, bad}) => {
return (
<div>
<Statistic text = "good" val = {good.toFixed(2)} />
<Statistic text = "neutral" val = {neutral.toFixed(2)} />
<Statistic text = "bad" val = {bad.toFixed(2)} />
<Statistic text = "total" val = {total.toFixed(2)} />
<Statistic text = "average" val = {((good - bad) / total).toFixed(2)} />
<Statistic text = "positive" val = {(good / total).toFixed(2) + "%"} />
</div>
)
}
When you pass your value like this val = {good / total + "%"}, it gets converted to a string due to type coercion that's why you're not able to use toFixed on val
Either you can use toFixed while passing the prop
<Statistic text = "positive" val = {(good / total).toFixed(2) + "%"} />
Or, you can pass unit separately, and use toFixed on the 'val'
const Statistic = ({text, val, unit}) => {
return (
<table>
<tbody>
<tr>
<td>{text}</td>
<td> {parseFloat(val).toFixed(2)} + unit}</td>
</tr>
</tbody>
</table>
)
}
//......
<Statistic text = "average" val = {(good - bad) / total} unit="" />
<Statistic text = "positive" val = {(good / total)} unit="%" />

How to compute the total average of per students using Javascript

I hope my title is enough to understand my question, ive been post this question several times but I hope this time I get help from you guys.
<table id="blacklistgrid" border="2px">
<tr>
th id="th">Students Name</th>
</tr>
{% for student in teacherStudents %}
<tr class="tr2">
<td id="td">{{ student.Students_Enrollment_Records.Student_Users }}</td>
</tr>
{% endfor %}
</tr>
</table>
<button type="button" value="" class="save" onclick="doTheInsert()" title="Insert New Cell" id="save">&plus; Insert New Cell</button>
<button type="button" value="" class="save" onclick="undoTheInsert()" title="Undo Recent Action" id="unsave">× Undo Recent Action</button>
<button type="button" value="" class="save" onclick="compute()" title="Undo Recent Action" id="compute">Compute</button>
this is my script for the button Insert Cell
<script>
var counter = 0;
function doTheInsert(){
let header=$("tr#tr"); // same as $.find("tr[id='tr2']")
$('#th').after("<th data-id='headers' id='header'><input type='date'></th>");
let rows=$(".tr2");
rows.append("<td data-id='row' ><input type='number' class='average'/></td>");
counter++;
}
</script>
this is my current script in computation
<script>
function compute(){
var sum = 0;
var rows = document.getElementsByClassName("average");
for(var i = 0; i < counter; i++){
sum += parseInt(rows[i].value);
}
var average = sum / counter;
document.getElementById("demo").innerHTML = average;
let header=$("tr#tr"); // same as $.find("tr[id='tr2']")
$('#thb').before("<th data-id='headers' id='headerss'>Average</th>");
}
</script>
this is the results I get ,
it only compute the average of first students
this is the result I want
when ive tried this query from Mr #Saket Yadav
<script>
function compute(){
var sum = 0;
var rows = document.getElementsByClassName("average");
$(".average").each(function () {
sum += parseInt($(this).val());
});
console.log("Sum:"+sum);
var average = sum / counter;
document.getElementById("demo").innerHTML = average;
let header=$("tr#tr"); // same as $.find("tr[id='tr2']")
$('#thb').before("<th data-id='headers' id='headerss'>Average</th>");
}
</script>
ive got this result
this is what i want in my result
Kindly update your JavaScript compute function as shown below.
<script>
function compute(){
let header=$("tr#tr"); // same as $.find("tr[id='tr2']")
$('th:last-child').after("<th data-id='headers' id='header'>Average</th>");
let rows1=$(".tr2");
rows1.append("<td data-id='row' ><input type='number' class='averages'/></td>");
$('tr').each(function () {
var sum = 0
var i=0;
$(this).find('.average').each(function () {
var textVal = $(this).val();
console.log(textVal);
if (!isNaN(textVal) && textVal.length !== 0) {
sum += parseFloat(textVal);
}
i++;
});
var avg=sum/i;
$('.averages', this).val(avg);
});
}
</script>
I would do something like this (using react instead of jquery)
const App = () => {
const [ students, setStudents ] = React.useState([{
id: 0,
name: 'Jonh',
notes: [
{ date: '2020-01-02', note: 4 },
{ date: '2020-01-03', note: 12 }
]
}]);
const [ days, setDays ] = React.useState([
'2020-01-02',
'2020-01-03'
])
const [ newStudentName, setNewStudentName ] = React.useState('Name')
const getAverage = notes => {
return notes.reduce((sum, { note }) => sum + parseInt(note), 0)/notes.length;
}
const addStudent = () => {
setStudents([
...students, {
id: +new Date(),
name: newStudentName,
notes: days.map(date => ({ date, note: null }))
}
])
}
const setNote = (studentId, noteDate, newNote) => {
const nextStudents = students.map(student => {
if (student.id === studentId) {
return {
...student,
notes: student.notes.map(({ note, date }) => (
date === noteDate ? { note: newNote, date } : { note, date }
))
}
}
return student;
})
setStudents(nextStudents)
}
return (
<div>
<table>
<tr>
<th>Name</th>
{days.map(day => <th>{day}</th>)}
<th>Average</th>
</tr>
{students.map(({ id: studentId, name, notes })=> (
<tr>
<td>
{name}
</td>
{notes.map(({ date, note }) => <td><input type="number" onChange={({ target: { value }}) => setNote(studentId, date, value)} value={note} /></td>)}
<td>{getAverage(notes)}</td>
</tr>
))}
</table>
<input onChange={({target: { value }}) => setNewStudentName(value)} value={newStudentName} />
<button onClick={() => addStudent()}>Add student</button>
</div>
)
};
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Checkbox default checked state issue

I am having the following code. There's some issue I'm facing right now with this thing in my project. I want to use the forEach() loop there inside getElements() instead of map() and also I want to simply show it default checked whenever after checking on a checkbox, going next and again returning back there.
Any help with this issue ??
here's the => DEMO
import React, { Component } from 'react';
import { render } from 'react-dom';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import Checkbox from 'material-ui/Checkbox';
class App extends Component {
itemsPerPage = 4
constructor(props) {
super(props);
var ids = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
this.state = {
ids: ids,
idsChecked: ids.map(() => false),
page: 0
}
}
componentDidMount = () => {
}
handlePrevious = () => {
this.setState({ page: this.state.page - 1 });
}
handleNext = () => {
this.setState({ page: this.state.page + 1 });
}
handleCheck = (e) => {
var id = Number(e.currentTarget.id);
var idsChecked = this.state.idsChecked.map((bool, i) => i === id ? !bool : bool);
this.setState({ idsChecked: idsChecked });
}
handleDetails = (e) => {
var id = Number(e.currentTarget.getAttribute("rel"));
console.log("even or odd is clicked! (button #id: " + id + ")");
}
getElements = () => {
var first = this.state.page * this.itemsPerPage;
var trs = this.state.ids.slice(first, first + this.itemsPerPage).map((element, i) => {
let details = <button rel={first + i} onClick={this.handleDetails}> {element % 2 ? "odd" : "even"} </button>;
return (
<tr key={element}>
<td><Checkbox
checked={this.state.idsChecked[first + i]}
id={first + i}
onCheck={this.handleCheck}
/></td>
<td>{element}</td>
<td>{details}</td>
</tr>
);
});
return trs;
}
render() {
var hasPrevious = this.state.page > 0;
var hasNext = this.state.page < Math.floor((this.state.ids.length - 1) / this.itemsPerPage);
var tdStyle = {width: "80px"}
return (
<div>
<div>
<table>
<tbody>
<tr>
<td style={tdStyle}>{hasPrevious && <button onClick={this.handlePrevious} hidden={this.state.hasPrevious}> Previous </button>}</td>
<td style={tdStyle}>{hasNext && <button onClick={this.handleNext} hidden={this.state.isNext}> Next </button>}</td>
</tr>
</tbody>
</table>
</div>
<div>
<table>
<tbody>
{this.getElements()}
</tbody>
</table>
</div>
</div>
);
}
}
render(<MuiThemeProvider><App /></MuiThemeProvider>, document.getElementById('root'));
To replace, map with forEach, push the checkbox elements onto an array, and return that array from your getElements().
Use the defaultChecked props of the <Checkbox> component to set the default value to true.
Full code:
getElements = () => {
var first = this.state.page * this.itemsPerPage;
let checkboxArray = []; // array for storing the elements
this.state.ids.slice(first, first + this.itemsPerPage).forEach((element, i) => {
let details = <button rel={first + i} onClick={this.handleDetails}> {element % 2 ? "odd" : "even"} </button>;
checkboxArray.push(
<tr key={element}>
<td><Checkbox
checked={this.state.idsChecked[first + i]}
id={first + i}
defaultChecked={true/*use the defaultChecked prop*/}
onCheck={this.handleCheck}
/></td>
<td>{element}</td>
<td>{details}</td>
</tr>
);
});
return checkboxArray; // return the array
}
render() {
var hasPrevious = this.state.page > 0;
var hasNext = this.state.page < Math.floor((this.state.ids.length - 1) / this.itemsPerPage);
var tdStyle = {width: "80px"}
return (
<div>
<div>
<table>
<tbody>
<tr>
<td style={tdStyle}>{hasPrevious && <button onClick={this.handlePrevious} hidden={this.state.hasPrevious}> Previous </button>}</td>
<td style={tdStyle}>{hasNext && <button onClick={this.handleNext} hidden={this.state.isNext}> Next </button>}</td>
</tr>
</tbody>
</table>
</div>
<div>
<table>
<tbody>
{this.getElements()}
</tbody>
</table>
</div>
</div>
);
}
}

Categories