How to prevent random numbers from generating again? - javascript

I am building some kind of calculator for 24 game. Everything was fine until I added, onClick to the button to store the state.
Here's the fullcode:
import React from "react";
import { useState } from "react";
export default function DuaEmpat() {
const [calc, setCalc] = useState("");
const [result, setResult] = useState("");
const ops = ["/", "*", "+", "-"];
const ns = () => Math.floor(Math.random() * (9 - 1 + 1)) + 1;
const updateCalc = (value) => {
setCalc(calc + value);
};
const createDigits = () => {
const digitz = [];
for (let i = 1; i < 5; i++) {
digitz.push(
<button
onClick={() => updateCalc(ns().toString())}
className=""
key={i}
>
{ns()}
</button>
);
console.log(digitz);
}
return digitz;
};
return (
<>
<main>
<div className="App ">
<div className="Calculator-24 ">
<div className="display">
{result ? <span className="text-gray-400">(0)</span> : ""}{" "}
{calc || "0"}
</div>
<div className="operators r">
<button className="flex-1" onClick={() => updateCalc("/")}>
/
</button>
<button className="flex-1" onClick={() => updateCalc("*")}>
*
</button>
<button className="flex-1" onClick={() => updateCalc("+")}>
+
</button>
<button className="flex-1" onClick={() => updateCalc("-")}>
-
</button>
<button className="flex-1">DEL</button>
</div>
<div className="digits flex flex-wrap appearance-none border-none outline-none bg-zen-tertiary font-bold text-white text-2xl p-4 cursor-pointer">
{eval(createDigits())}
<button
onClick={() => window.location.reload()}
className=" p-4 ">
acak ulang
</button>
<button className=" p-4 ">=</button>
</div>
</div>
</div>
</main>
</>
);
}
Any idea on how to stop the value of generated numbers( ns() ) to be unchanged?

I think this is what you are trying to do? Please tell me if i misread your question.
In your code you have a ns() function. Every time that runs, it will create a new random number. So lets break down the code you wrote.
<button
onClick={() => updateCalc(ns().toString())} //When clicked this will run the ns function and create a new number. Then it will pass that value to the updateCalc funtion.
className=""
key={i}
>
{ns()} //this is calling the ns function AGAIN to create a new value. Therefore your onClick value and your button text will always mismatch.
</button>
So how can we fix your code? Lets only call the function once!
for (let i = 1; i < 5; i++) {
let randomNumber = ns().toString(); //Calls the function ONCE for every for loop and saves the return to a variable.
digitz.push(
<button
onClick={() => updateCalc(randomNumber)} // Lets use that variable from earlier
className=""
key={i}
>
{randomNumber} // lets use that same variable again.. Remember the function was only ran once during this loop. so the variable will be EXACTLY the same until the next loop.
</button>
);
console.log(digitz);
}

Related

React updating state/setState in .map causes infinite loop

I realize that setTotal(newState) is causing an infinite loop because when it gets updated, it re-renders > causes the function 'showDiscounted' to be called again > state gets updated > and it just goes on forever.
I know that I have to use useEffect to stop this issue, but I'm just not quite sure how I can implement it. I've thought about using useRef because it doesn't cause rerender, but I need it to rerender to show the state 'total' somewhere else. What is the best way to go about this?
import { useEffect, useState } from "react";
const QuoteCalcBot = ({ parts, setParts, option, discount }) => {
const [total, setTotal] = useState([0,0,0]);
const [discountedTotal, setdiscountedTotal] = useState([0,0,0]);
//change individual discount for a part
const handleDiscountChange = (part, e) => {
console.log(e.target.value);
console.log(part);
}
//takes in the full price then shows the discounted price of a part. Also adds up the discounted price to discountedTotal for displaying it later
const showDiscounted = (price) => {
const temp = Math.ceil((price * ((100 - discount) / 100)) / 36)
const newState = discountedTotal.map((t, i) => i === option? t + temp : t)
console.log(newState);
setTotal(newState); //this causes infinte loop ERROR
return (
<div className="col-2">
{temp}
</div>
)
}
const addToTotal = (price) => {
//we need to add the full prices to total so it can display
return (
Math.ceil(price / 36)
)
}
//this also works.
//const showParts = (activeIndex) => { return parts.map(part => part.active[activeIndex] && <div key={part.bodyPart}>{part.bodyPart}</div>) }
const showParts = (activeIndex) => {
return parts.map(
(part) => part.active[activeIndex] && (
<div key={part.bodyPart} className="row">
{/* body part */}
<div className="col-3">{part.bodyPart}</div>
{/* original full price */}
<div className="col-2 text-decoration-line-through">${addToTotal(part.price)}</div>
{/* discounted price */}
<div className="col-2">
${(showDiscounted(part.price))}
</div>
{/* choose discount */}
<div className="col-auto">
<select className="form-select-sm" aria-label="Default select example" /* value={discount} */ onChange={(e) => handleDiscountChange(part, e)}>
<option defaultValue>{discount}</option>
<option value="30">30%</option>
<option value="40">40%</option>
<option value="50">50%</option>
<option value="60">60%</option>
</select>
</div>
<div className="col-1 text-end"><button type="button" className="btn-close" aria-label="Close"></button></div>
</div>)
)
}
return (
<div className="">
<div className="row rows-cols-3">
{/* to keep this part cleaner, having a separate state that filters out is ideal */}
<div className="col-4">
{showParts(0)}
</div>
<div className="col-4">
{showParts(1)}
</div>
<div className="col-4">
{showParts(2)}
</div>
{/* TOTAL */}
<div className="col-4">discounted total for 36 months: {total[0]}</div>
<div className="col-4">total, per month, cost, savings</div>
<div className="col-4">total, per month, cost, savings</div>
</div>
</div>
);
}
export default QuoteCalcBot;
Can try to update the state from the event handler(source of the event)
//passing extra params to event handler
const handleDiscountChange = (part, e, partPrice) => {
// this may need extra props to update the price for the right part
const newDiscount = e.target.value
// put this in a common function
const calcPartPrice = Math.ceil((partPrice * ((100 - newDiscount) / 100)) / 36)
const newState = discountedTotal.map((t, i) => i === option? t + calcPartPrice : t)
setTotal(newState);
// update discount
console.log(e.target.value);
console.log(part);
}
better to move this outside as a seperate component
const ShowParts = ({ parts = [], activeIndex, discount, handleDiscountChange, addToTotal }) => {
return parts.map(
(part) => {
const partPrice = Math.ceil((part.price * ((100 - discount) / 100)) / 36)
return part.active[activeIndex] && (
<div key={part.bodyPart} className="row">
{/* body part */}
<div className="col-3">{part.bodyPart}</div>
{/* original full price */}
<div className="col-2 text-decoration-line-through">${addToTotal(part.price)}</div>
{/* discounted price */}
<div className="col-2">
<div className="col-2">
{partPrice}
</div>
</div>
{/* choose discount */}
<div className="col-auto">
<select className="form-select-sm" aria-label="Default select example" /* value={discount} */
// pass partPrice and other props needed to handler to help in total calculations
onChange={(e) => handleDiscountChange(part, e, part.price)}
>
<option defaultValue>{discount}</option>
<option value="30">30%</option>
<option value="40">40%</option>
<option value="50">50%</option>
<option value="60">60%</option>
</select>
</div>
<div className="col-1 text-end"><button type="button" className="btn-close" aria-label="Close"></button></div>
</div>)
})
}
usage inside main app
<div className="col-4">
<ShowParts
parts={parts}
activeIndex={0}
discount={discount}
handleDiscountChange={handleDiscountChange}
addToTotal={addToTotal}
/>
</div>
The code can be optimized further, but I hope this helps you in some way

How do I update a specific index

So I'm practising react with a simple task master app, where I add each user input as a new task in an array of tasks. In the app, I have Add, Delete, and Update buttons.
Everything seems to be working fine except for the update function, it updates the last index of the array instead of the specific index I clicked.
Here is my JSX
const JsxElement = task.map((eachTask, index) => {
return (
<Fragment key={index}>
<div key={index} className="table-data-container">
<div className="item-data">{eachTask}</div>
<div className="item-data">{date}</div>
<div className="item-data">
<div className="btn-data-container">
<div className="btn-data">
<div className="btn" onClick={() => deleteTask(index)}>Delete</div>
</div>
<div className="btn-data">
<div className="btn" onClick={() => UpdateTaskBtn(eachTask, index)}>Update</div>
</div>
</div>
</div>
</div>
<br/>
{task.length - 1 === index &&
<div className="input-update-container">
<div className="input-area">
<input
ref={inputRef}
type="text"
/>
</div>
<div className="btn-update-add-container">
{update ?
<div className="btn-add" onClick={() => handleTaskUpdate(eachTask, index)}>Update
Task</div>
:
<div className="btn-add" onClick={handleTask}>Add Task</div>
}
</div>
</div>
}
</Fragment>
)
})
The first update button function prepares the input, sets the task to be updated and makes the update button visible. The second one is where I want the update action to happen once clicked.
function UpdateTaskBtn(eachTask) {
inputRef.current.value = eachTask
setUpdate(true)
}
function handleTaskUpdate(e, index) {
const list = [...task]
list[index] = inputRef.current.value
setTask(list)
inputRef.current.value = ""
setUpdate(false)
}
I want to be able to set the task to the specific index I want to update.
based on this line of code
{task.length - 1 === index &&
you are checking if the index is the last index , so you are passing the last index to the handleTaskUpdate function. so you can define a updateIndex state
const [updateIndex,setUpdateIndex] = useState()
then your function should look like this
function UpdateTaskBtn(eachTask,index) {
inputRef.current.value = eachTask
setUpdateIndex(index)
setUpdate(true)
}
function handleTaskUpdate(e, index) {
const list = [...task]
list[updateIndex] = inputRef.current.value
setTask(list)
inputRef.current.value = ""
setUpdate(false)
}
do not forget to pass index to UpdateTaskBtn function in onClick event
Try this
function handleTaskUpdate(e, index) {
const value = inputRef.current.value
const updatedTasks = task.map((task, i) => i === index ? value : task)
setTask(updatedTasks)
inputRef.current.value = ""
setUpdate(false)
}

React Tab is not letting me edit text field, getting out of focus

When i type, the textbox is getting out of focus. I am able to type only one letter at a time. Following is my code: -
<TabPanel value={value} index={0}>
{[...Array(commentCount),].map((item, index) => {
return (
<>
<div className="col-12 d-flex comments-content">
<div className="mb-0 flex-10">
<textarea name="" id="" rows="4" className="w-100 p-2 mb-3" data-testid={"commentTextArea_" + index}
value={ commentArea.find(x=>x.id==index)?.value==undefined?"":commentArea.find(x=>x.id==index)?.value}
onChange={(e)=>{setComment(e,index)}}/>
</div>
</div>
</div>
and my js code is :-
const [commentArea,setCommentArea]=useState([{value:"",id:0}]);
const setComment=(e,index)=>{
const searched= commentArea.find(x => x.id === index);
if(searched!="" && searched!=undefined){
searched.value=e.target.value;
}
else{
let res={
value:e.target.value,id:index
}
commentArea.push(res);
}
setCommentArea([...commentArea]);
}
Don't use keys that are constantly changing.
You can read about keys here: Keys
Your textfield should have a event handler function looking like this:
const handleChange = (event) => {
setCommentArea(event.target.value);
};

Make a button expand and collapse in map function in ReactJS

I am working on a project as a means to practice some stuff in react and I need to render a button for each of the map data. I did this successfully but expand and collapse has been giving me issue. Whenever I click on the button all data collapse and expand together.
const DataFetch = () => {
...
const [btnValue, setBtnValue] = useState('+');
const handleChange = (e) => {
setShowData(!showData);
setBtnValue(btnValue === '+' ? '-' : '+');
};
return (
<div className='container'>
...
{studentResults
.filter((val) => {
if (searchTerm === '') {
return val;
} else if (
val.firstName.toLowerCase().includes(searchTerm.toLowerCase()) ||
val.lastName.toLowerCase().includes(searchTerm.toLowerCase())
) {
return val;
} else {
return null;
}
})
.map((student) => {
return (
<div key={student.id}>
<div className='card'>
<div className='row'>
<div className='col-2'>
<div className='pic'>
<img src={student.pic} alt='avatar' />
</div>
</div>
<div className='col'>
<div className='details'>
<p className='name'>
{student.firstName.toUpperCase()}{' '}
{student.lastName.toUpperCase()}
</p>
<div className='sub-details'>
<p>Email: {student.email}</p>
<p>Company: {student.company}</p>
<p>Skill: {student.skill}</p>
<p>
Average:{' '}
{student.grades.reduce(
(a, b) => parseInt(a) + parseInt(b),
0
) /
student.grades.length +
'%'}
</p>
<button onClick={handleChange} className='showBtn'>
{btnValue}
</button>
{showData && (
<div>
<br />
{student.grades.map((grade, key) => {
return (
<p key={key}>
Test {key + 1}:   {grade}%
</p>
);
})}
</div>
)}
...
Collapse Image
Expand Image
All the elements expand and collapse together because you assign to all of them the same state showData state.
One solution would be to add a new field to your data (so inside student) that is true or false when you want to expand or collapse the single student.
Another solution would be to create the showData state as an array where each element correspond to a different student. When you click the button, in this case, you pass to the function for example the id and with that you link your student to the right element inside the showData.

How to Add a line strikethrough or modify css in React Functional Component

I am trying to add line strike though or change background colour of a list element when a corresponding tick button is clicked kindly suggest a way to do so ..
Is there any way similar to jquery's ex - $("p").css("background-color");
& for beginners which will be more suitable to grasp who is coming from jq background Class component or Functional
const [input, setValue] = useState("");
const [todos, setTodos] = useState([]);
// passing entered
const handleInput = (event) => {
setValue(event.target.value);
};
const lp = (event) => {
// let c = [1,2,34,55]
event.preventDefault();
// if no input nothing will happen return none
if (!input) return;
// using spread operator its used whenever we need to add array data to exiting array
const newTodos = [...todos, input];
setTodos(newTodos);
// clearing input text
setValue("");
};
const handeldel=(index) => {
// console.log(index)
todos.splice(index, 1);
setTodos([...todos]);
// const newTodos = todos.splice(index, 1);
// setTodos([...newTodos]);
}
const markdone =() => {
// how to mark complete
}
return (
<div>
<h1 className="text-center font-weight-bolder alert-info mb-5">Tasks To Do <i class="fas fa-clipboard-list text-success"></i></h1>
<div class="input-group mb-3 container">
<input
className="form-control border-primary font-weight-bold"
style={{ height: 60 }}
placeholder="Enter Text here"
type="text"
value={input}
onChange={handleInput}
/>
<div class="input-group-append">
<button
className="input-group-append font-weight-bolder "
style={{ fontSize: 20 }}
onClick={lp}
>
{" "}
<i class="fas fa-plus-square fa-2x p-2"></i>{" "}
</button>
</div>
</div>
{todos.map((x,index) => (
<ol style={{ listStyle:"outside" }} className="container">
<li className="font-weight-bolder table-bordered text-capitalize alert-secondary " style={{fontSize:30}}>
{ x }<i class="fas fa-check-circle float-md-right text-success" onClick={markdone}></i> <i class="fas fa-trash-alt text-danger float-md-right" onClick={()=>handeldel(index)}></i>
</li>
</ol>
))}
{/* for future ref */}
{/* <div >
{data.map((user) => (
<div className="user">{user.id + " " + user.name
}</div>
))}
</div> */}
</div>
);
I created the code for you here
https://codesandbox.io/s/pensive-chaplygin-ry5ed?file=/src/App.js
The main things i changed is that i added an Id using uuid(npm i uuid) to each new li as well as a completed state.
Also I passed an event parameter to the markdone function
const markdone = (e) => {
const element = todos.findIndex((el) => el.id === e.target.id);
const newToDos = [...todos];
console.log(element);
newToDos[element] = {
...newToDos[element],
completed: !newToDos[element].completed
};
setTodos(newToDos);
};
So First i am trying to find the index of the element target element by compairing the ID's.
then I am cloning the array and using the spread operator I am returning the element but changing the completed state to the oposite. So if it is true, then it will become false and vice-versa.
On the <li> I am passing a conditional classname based on the state of "completed".
So if completed is true then it will get a class of "li-completed li" if it isnt true then it will get a class of 'li'. You can target these classes on css ofcourse.
Please have a look at the link above you to get the full changes.
thanks for helping me out got an even simpler method to achieve using just toggle functionality as below on the mark done function might be useful for beginner's
const [line, setline] = useState(false);
const markdone = (index) => {
console.log(todos,index)
setline(!line);
// setll(true);
};

Categories