I am Making a Table in react Js using the .map()function. In the last <td> of the table, I am calculating the taxed amount of the product from a function. This amount fills in every row <td> specified for them.
{
(!cr.items.length < 1) &&
cr.items.map((item , ind) => {
return(
<td scope="col">{(item) ? taxedValue(item.quantity , item.salesInfo.unitPrice , item.discount , item.tax) :null}</td>
</tr>
)
})
}
Function taxedValue return a new amount. There can be many rows for this. What I want is to Get the sum of all the previously taxedValue rows.
Like When Map returns these row
<tr><td>1<td></td>
<tr><td>2<td></td>
I want to add {1+2} = 3(//get this as my total amount to access it anywhere in the code//}
I tried calling a state function with an initial value 0. But gets an error Too much re-render.
It would be easy to simply add totaling a sum in the array.prototype.map function, but this callback is to be a pure function without side-effects.
I would suggest just computing the total separately using array.prototype.reduce. If computing the taxed value twice per render cycle is prohibitive then refactor the code to compute the taxed value once and inject it into the data you want to map and add it to a total sum.
const computeTotalTaxedValue = cr.items.reduce(
(total, item) => item ? taxedValue(item.quantity, item.salesInfo.unitPrice, item.discount, item.tax) : 0,
0,
);
If computing the taxed value twice for each item twice per render cycle is prohibitive then compute it once and inject it into the data and compute a sum.
const [totalTaxedValue, setTotalTaxedValue] = useState(0);
const [taxedValues, setTaxedValues] = useState([]);
useEffect(() => {
let totalTaxedValue = 0;
const taxedValues = [];
cr.items.forEach(item => {
const taxValue = item ? taxedValue(item.quantity, item.salesInfo.unitPrice, item.discount, item.tax) : null;
totalTaxedValue += taxValue;
taxedValues.push(taxValue);
});
setTotalTaxedValue(totalTaxedValue);
setTaxedValues(taxedValues);
}, [cr.items]);
...
{
(!taxedValues.length < 1) &&
taxedValues.map((taxValue, ind) => <td scope="col">{taxValue}</td>
})
}
I think you can do that in a separate function to keep that logic isolated. What you can do is define a variable and increment the value in every iteration of the map and then, in the last element, render an additional column. Something like this:
const renderColumns = () => {
let total = 0;
if (!cr.items.length < 1) {
return cr.items.map((item , ind) => {
const value = item ? taxedValue(item.quantity , item.salesInfo.unitPrice , item.discount , item.tax) : null;
total += value || 0;
return (
<>
<td scope="col">{value}</td>
{ind === cr.items.length - 1 ? (
<td>{total}</td>
) : null}
</>
);
});
}
return null;
};
Related
I want to display objects from the array one by one by clicking on the card.
To do this, I made a check for array length (props.tasks.length), and if it is more than 1, then the array is shuffled and then I want to display these objects one by one on click.
But after a click, a new array is generated each time and often the objects are repeated 2-4 times.
But I get an error: Too many re-renders. React limits the number of renders to prevent an infinite loop.
function App(props) {
let [random, setRandom] = useState({});
let [newArr, setNewArr] = useState(props.tasks.sort(() => Math.random() - 0.5));
let i = 0;
random = newArr[i]
setRandom(newArr[i])
const randomCard =()=> {
console.log(random);
console.log(i);
console.log(newArr[0],newArr[1],newArr[2],newArr[3], newArr[4], newArr[5]);
console.log(newArr[i]);
if (i <= newArr.length) {
i++
} else {
newArr = props.tasks.sort(() => Math.random() - 0.5);
console.log('clean');
i=0
}
}
return (
<div>
{newArr.length > 1 ? (
<div className="item" onClick={randomCard}>
<p>{random.name}</p>
<p>{random.translate}</p>
<p>{random.note}</p>
</div>
) : (
<p>Nothing</p>
)}
</div>
);
}
export default App;
The main culprit in your code example is
setRandom(newArr[i])
Using a hook's set method should only be done via an action or wrapped in a useEffect for side effects. In your code, setNewArr is called on every rerender and will cause the component to rerender... causing the infinite loop of rerenders.
You also don't need to store the selected element from your array in a state, you're essentially doing this by just storying the index in the use state.
Your solution should look something like this.
Also you want to reset i when it's < newArr and not <= newArr because if your array is of length "2" and i is "2" then you're setting i to "3" which doesn't point to any element in your list.
const shuffle = (arr) => arr.sort(() => Math.random() - 0.5);
function App(props) {
// Default empty list
const [newArr, setNewArr] = useState([]);
// Sets the default index to 0
const [i, setI] = useState(0);
// We don't want to call shuffle on every rerender, so only setNewArr
// Once when the component is mounted.
useEffect(() => {
setNewArr(shuffle(props.tasks));
}, []);
const randomCard =()=> {
if (i < newArr.length) {
setI(i + 1);
} else {
setNewArr(shuffle(props.tasks));
setI(0);
}
}
return (
<div>
{newArr.length > 1 ? (
<div className="item" onClick={randomCard}>
<p>{newArr[i].name}</p>
<p>{newArr[i].translate}</p>
<p>{newArr[i].note}</p>
</div>
) : (
<p>Nothing</p>
)}
</div>
);
}
export default App;
SOLUTION: Update the key value for the input element to refresh the default value => content of the input element. Deleting an element from the array DID work. Thanks for your help!
src: https://thewebdev.info/2022/05/12/how-to-fix-react-input-defaultvalue-doesnt-update-with-state-with-javascript/#:~:text=state%20with%20JavaScript%3F-,To%20fix%20React%20input%20defaultValue%20doesn't%20update%20with%20state,default%20value%20of%20the%20input.
I got an useState array in my code which represents a lisst of students:
const [students, setStudents] = useState([""]);
This array gets mapped to student elements:
{students.map((student, index) => <Student setStudents={setStudents} students={students} id={index} key={index} content={student} />)} I also got an AddStudent element which adds students to the array.
function AddStudent(props) {
const {setStudents} = props;
return (
<button className="change-student add-student" onClick={() => {
setStudents((students) => [...students, ""])
}}>
+
</button>
);
}
The RemoveStudent component is supposed to remove a student by its index in the array. I've tried many different ways but none worked correctly. How can I get it to work? Here is my code:
function RemoveStudent(props) {
const {students, setStudents, id} = props;
return (
<button className="change-student remove-student" onClick={() => {
let data = students;
if(id > -1) {
data.splice(id, 1);
}
console.log(data)
// setStudents(data)
// alternative:
// setStudents(students.filter(index => index !== id)); // removes the last element in the list
// doesn't work properly
}}>
-
</button>
)
}
Thanks for your help!
2 things should be noted here:
While updating react state arrays, use methods that return a new array (map, filter, slice, concat),
rather than ones that modify the existing array (splice, push, pop, sort).
While updating React state using its previous value, the callback argument should be used for the state setter. Otherwise you may get stale values. (See React docs).
if(id > -1) {
setStudents(students=> students.filter((s,i)=>(i != id)))
}
Consult this article, for a complete reference about how to update React state arrays.
You need to copy the students array first and then try removing the student by index. I assume by id you mean index at which to remove the student. Then you can try something like:
function RemoveStudent(props) {
const {students, setStudents, id} = props;
return (
<button
className="change-student remove-student"
onClick={() => {
if(id > -1) {
const data = [...students]; // making a copy
data.splice(id, 1); // removing at index id
console.log(data)
setStudents(data)
}
}}
>
-
</button>
)
}
With array.filter() you have a mistake in how you pass callback to filter() method. Please try the following:
setStudents(students.filter((,index) => index !== id));
Notice the index is second param of the callback so I used a , before index.
After #Irfanullah Jan 's answer you should make sure how you show the student.
Here is the simple example:
const [students, setStudents] = useState([1, 2, 3]);
return (
<div>
{students.map((student, index) => {
return <div>{student}</div>; // show the value not the index
})}
<button
onClick={() => {
let id = 1;
const copy = [...students];
copy.splice(id, 1)
console.log(copy)
setStudents(copy);
}}
>
-
</button>
</div>
);
The code above will delete the student of "index==1"
So I have a JSON object that is dynamically creates based on lots. I'm trying to get the total cost of each lot. Each object in the list is a different purchase.
var lot_list = [{lot:123456,cost:'$4,500.00'}, {lot:654321, cost:'$1,600.00'}, {lot:123456, cost:'$6,500.00'}]
I want the total cost for each lot so I tried
var totalBalances = {};
function addBalance(){
lot_list.forEach(function(lots){
totalBalances[lots[lot]] += parseFloat(lots['cost'].replace('$','').replace(',',''));
});
}
This ends with every lot having a null cost
I also tried
var totalBalances = {};
function addBalance(){
lot_list.forEach(function(lots){
totalBalances[lots[lot]] = parseInt(totalBalances[lots[lot]]) + parseFloat(lots['cost'].replace('$','').replace(',',''));
});
}
Neither of these worked any help is much appreciated.
You cannot get a value to sum with parseFloat('$4,500.00') because of the invalid characters. To remove the dollar sign and commas you can replace using;
> '$4,500.00'.replace(/[^\d\./g,'')
> "4500.00"
Here the regex matches anything that is not a digit or decimal place with the global modifier to replace all occurrances.
You can map your array of objects to the float values using Array.map() to get an array of float values.
> lot_list.map((l) => parseFloat(l.cost.replace(/[^\d\.]/g,'')))
> [4500, 1600, 6500]
With an array of float values you can use Array.reduce() to get the sum;
> lot_list.map((l) => parseFloat(l.cost.replace(/[^\d\.]/g,''))).reduce((a,c) => a+c)
> 12600
EDIT
To get totals for each lot map an object with the lot id included and then reduce onto an object with;
> lot_list
.map((l) => {
return {
id: l.lot,
cost: parseFloat(l.cost.replace(/[^\d\.]/g,''))
}
})
.reduce((a,c) => {
a[c.id] = a[c.id] || 0;
a[c.id] += c.cost;
return a;
}, {})
> { 123456: 11000, 654321: 1600 }
Here the reduce function creates an object as the accumulator and then initialises the sum to zero if the lot id has not been summed before.
based on your code
var totalBalances = {};
function addBalance(){
lot_list.forEach(function(lots){
// totalBalances[123456] is not defined yet, this should gets you problem if you're trying calculate its value
// totalBalances[lots[lot]] += parseFloat(lots['cost'].replace('$','').replace(',',''));
// maybe you should use this instead
totalBalances[lots[lot]] = parseFloat(lots['cost'].replace('$','').replace(',',''));
});
}
but, if you want to count total value of cost, you might considering using array reduce
function countCost(){
return lot_list.reduce((accum, dt) => {
return accum + parseFloat(l.cost.replace(/[^\d\.]/g,''))
}, parseFloat(0));
} //this gonna bring you total count of all cost
Hey can anyone tell me why I'm getting repeated values for both oppDesc and contestEntriesAmt even though I'm using a Set for oppDesc (contestEntriesAmt doesn't need to be in a Set because nothing's being repeated)?
Why are both oppDesc and contestEntriesAmt repeating? It just doesn't make sense.
Whenever I remove entries.map(() => {...}), oppDesc shows up correctly (no repeated values).
const oppDescData = () => {
const dataOppDesc = oppDesc
let desc = [];
dataOppDesc.forEach((oppD) => {desc.push(oppD.oppDescription);});
const dataNumEntries = numEntries
let entries = [];
dataNumEntries.forEach((entry) => {entries.push(entry.SumOfEntries);});
let filteredDesc = new Set(desc);
let oppDescription = [...filteredDesc];
return (
<>
{
oppDescription.map((oppDesc) => {
return entries.map((contestEntriesAmt) => {
return(
<tr>
<td>{oppDesc}</td>
<td>{contestEntriesAmt}</td>
</tr>
);
})
})
}
</>
);
};
You getting duplicates as you are doing intersection of two lists by calling map in map. Says you have two lists, each 10 items, in result with your code you will get 100 tr entries. Even if you will use sets, and each value will be unique per array - this still will happens
I am building a React application working with the reddit api and oAuth.
I am using MaterialUI, and I am attempting to use the Component to construct a 3 column grid of images that have dynamically generated column width values, max obviously being 3.
The first item in the array of fetched image posts will be given a key/value pair of a random number between 1 and 3. The second item will be given a key/value pair of a number completing the row, if the first item's value is != 3.
Then, it will start over again, the idea being that every time the grid loads, its first item might be 1 column wide, 2 columns wide, or the whole row of 3 columns wide, and the algorithm is supposed to complete the rest of the grid accordingly, meaning the rows all must add up to 3.
I have tried processing the array of posts in numerous ways, from assigning values to the first two objects in the array outside of a loop, then defining a 'last post' and 'post before that' variable to try to figure out a way to make the rows add up. I've tried to come up with a set of rules that would make this work regardless of array position, but cannot seem to come to an answer that doesn't have a few edge cases.
makeTiles() {
let posts = this.props.posts;
let postsWithCols = [];
posts[0].cols = Math.floor(Math.random() * 3 + 1);
console.log(posts[0])
postsWithCols.push(posts[0]);
let previousPost = postsWithCols[postsWithCols.length - 1];
switch(previousPost.cols) {
case 1:
console.log('is1');
posts[1].cols = Math.floor(Math.random() * 2 + 1);
postsWithCols.push(posts[1]);
break;
case 2:
console.log('is2');
posts[1].cols = 1;
postsWithCols.push(posts[1]);
break;
case 3:
console.log('is3');
posts[1].cols = Math.floor(Math.random() * 3 + 1);
postsWithCols.push(posts[1]);
break;
default:
console.log('something went wrong');
break;
}
let postBeforeThat = postsWithCols[postsWithCols.length - 2];
console.log(previousPost)
console.log(postBeforeThat)
console.log(postsWithCols)
}
render() {
this.makeTiles();
return (
<div>
open grid when i can get tileData figured out.
{/* <ImageGrid tileData={this.makeTiles()} {...this.props}/> */}
</div>
);
}
}
The only way I have ever had this kind of work, it kept alternating between 1 and 2 after the first initial tile.
There are couple of issues here. First of all, the code as presented really only will give you two post column values max. There is nothing looping or going back to fill in more columns. Second, I think what you are trying to do it have it create an array of posts that also have column values but the method never returns the final value (postsWithCols). I am going to assume you still just want a list of posts with column values and to not have it further split up in to rows.
let posts = this.props.posts;
// Const for the max
const maxColumns = 3;
// Variable to keep track of current remaining columns.
let remainingColumns = maxColumns;
// Go over each post and return the post with cols in to a new array.
let postsWithCols = posts.map((post, index) => {
// Special case, if last item, fill remaining
if (index === posts.length - 1) {
post.cols = remainingColumns;
return post;
}
// Get a random number for the remaining columns for this row
const cols = Math.floor(Math.random() * remainingColumns + 1);
post.cols = cols;
// Subtract the value
remainingColumns = remainingColumns - cols;
// If value is 0, reset to max.
if (remainingColumns <= 0) {
remainingColumns = maxColumns;
}
// Add to result array
return post;
});
console.log(postsWithCols);
There is more that could be tweaked about this, but this is the general idea.
I was able to come to this as a solution eventually.
getPostsWithColumns = (posts) => {
const postsWithColumns = posts.reduce((accum, post) => {
const lastIndex = accum.length - 1;
if (accum[lastIndex] && this.totalColumns(accum[lastIndex]) < 3) {
const currentTotal = this.totalColumns(accum[lastIndex]);
const postWithCols = {
...post,
cols: currentTotal === 2 ? 1 : this.randomNumber(2)
};
accum[lastIndex] = [...accum[lastIndex], postWithCols];
return accum;
}
const postWithCols = {
...post,
cols: this.randomNumber(3)
}
return [...accum, [postWithCols]];
}, []);
return postsWithColumns.reduce((accum, group) => [...accum, ...group], []);
};