This question already has an answer here:
React Component doesn't rerender after sorting array in store
(1 answer)
Closed 24 days ago.
The application has cards, each of which stores a number. I need to sort array by these numbers so that the application is re-rendered and the cards should be rendered from lowest numbers to highest numbers.
Code below:
App.js:
// array of numbers
const [cardsList, setCardsList] = React.useState([]);
// function to generate random numbers
const generateNumber = (maxNumber = 100) => {
let random = Math.random() * maxNumber;
return random;
};
// function to add cards to the card list
const addCard = () => {
let num = Math.floor(generateNumber());
setCardsList((prev) => [...prev, num]);
};
// function that need to sort cards
const sortCards = () => {};
CardList.js:
// cards rendering
<div className="cardList">
{props.cardList.map((number) => {
return (
<div className="card" key={number}>
<div className="card-close">
<img
style={{ cursor: "pointer" }}
width={16}
height={16}
src="/img/delete.png"
alt="closeBtn"
/>
</div>
<h1>{number}</h1>
</div>
);
})}
</div>
Have you tried .sort method?
in your case will be something like that:
const sortedCards = cardList.sort((a,b) => a.number > b.number)
// Please look into the below link
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
// You can try this with the general sort function.
const sortCards = () => {
if( cardsList.length > 0 ){
return cardsList.sort();
}
return -1;
};
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"
I come across the rendering issue with React State.
The problem is that {state} in return get value one beat late.
But the console log in handleChange shows right value.
If the previous value of state is 9, current value of state value is 10 then the console.log({state}) in handleChange shows 10 and the <span>{state}<span> in return shows 9.
It looks different from other state async problem.
I can't understand why this happened.
const [findText, setFindText] = useState("");
const [findCount, setFindCount] = useState(0);
const handleChange = (e) => {
let str = e.target.value;
setFindText(str);
let cnt = 0;
doxDocument.map((docx) => {
cnt += docx.src.split(findText).length - 1;
});
setFindCount(cnt);
console.log({findCount})
};
return(
<div>
<input
type="text"
value={findText}
onChange={handleChange}
/>
<span>{findCount} found <span>
</div>
);
Two problems...
findText will not have been updated to the new value when you use it in split(). Either use str instead or calculate findCount in a memo or effect hook with a dependency on findText.
You're completely misusing filter(). Use reduce() to calculate a computed sum
const [findText, setFindText] = useState("");
const findCount = useMemo(
() =>
findText
? doxDocument.reduce(
(sum, { src }) => sum + src.split(findText).length - 1,
0
)
: 0,
[findText, doxDocument] // may not need doxDocument
);
return (
<div id="App">
<input
type="text"
value={findText}
onChange={(e) => setFindText(e.target.value)}
/>
<span>{findCount} found</span>
</div>
);
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;
};
I have react component that gets index from map function, I'm trying to open div of row tag when my index is even and close it when my index is odd
render() {
return (
{this.props.index%2===0 && (<div className="row mt-1">)} //new row
<div className="col-1">{this.props.title}</div>
<div className="col-5">
<ProgressBar
variant={this.props.color}
now={this.props.now}
max={this.props.max}
label={this.props.label}
/>
</div>
{this.props.index%2===1 && (</div>)} //end of the row
);
}
this code in not compile:
enter image description here
the point is that every row contain two ProgressBar. What is the right way to do it?
You need to deal with whole elements are once, not tags.
This is easier if you break it up into functions.
You can use splice on an array to grab two items at a time.
e.g.
function createRow(elements) {
return (
<div>
{elements.map(createProgressBar)}
</div>
);
}
function createProgressBar(element) {
return (
<div>{element.index}</div>
);
}
function render() {
// ...
const rows = [];
while (myArray.length) {
const thisRow = myArray.splice(0,2);
rows.push(createRow(thisRow));
}
return rows;
}
You should modify the shape of your array into something like this before trying to render it.
[1,2,3,4] => [[1,2],[3,4]]
that way it would be easier for you to wrap it inside div.
see the live demo
Code to transform your flat array into nested array:
const list = [1, 2, 3, 4];
const [state, setState] = React.useState([]);
React.useEffect(() => {
let res = [];
for (let i = 0; i < list.length; i += 2) {
res.push([list[i], list[i + 1]]);
}
setState(res);
}, []);
Render logic:
{state.map(item => {
return (
<div style={{ border: "1px solid black", padding: "1em" }}>
{item.map(i => i)}
</div>
);
})}