Reputation: 235
I'm building a sorting algorithm visualizer, and in my return, I'm creating divs to represent vertical bars, in animatedBubbleSort() I'm swapping values in the state array on a timeout, the array IS being sorted, but what I expected to happen was that the .map function would be re-rendered each time the state is changed with updateArray(). But the .map function does not re-fire at all.
import React, { useState } from "react";
import "../styles/App.css";
import { Header } from "./Header";
export default function SortingVisualizer(props) {
const LOWER_BOUND = 5;
const UPPER_BOUND = 200;
const ARRAY_SIZE = 200;
const [array, updateArray] = useState(fillArrayWithRandomValues);
// returns a random number between bounds inclusive
function randomNumberBetweenBounds() {
return Math.floor(Math.random() * UPPER_BOUND) + LOWER_BOUND;
}
// fills array with random values
function fillArrayWithRandomValues() {
let tempArray = [];
for (let i = 0; i < ARRAY_SIZE; i++) {
tempArray.push(randomNumberBetweenBounds());
}
return tempArray;
}
function animatedBubbleSort() {
let tempArr = array;
let len = tempArr.length;
for (let i = 0; i < len; i++) {
for (let j = 0; j < len; j++) {
if (tempArr[j] > tempArr[j + 1]) {
let tmp = tempArr[j];
tempArr[j] = tempArr[j + 1];
tempArr[j + 1] = tmp;
setTimeout(() => {
updateArray(tempArr);
}, 300 * i);
}
}
}
}
return (
<div>
<Header bubbleSort={animatedBubbleSort} />
<div className="array-container">
{array.map((value, idx) => {
return (
<div
style={{ height: `${value * 2}px` }}
className="array-bar"
key={idx}
></div>
);
})}
</div>
</div>
);
}
Upvotes: 19
Views: 23253
Reputation: 107
This won't work in every situation but I managed to fix my similar issue by using a random number as the index.
Upvotes: 0
Reputation: 81
For anyone who comes across this and is already using an ID as their key, my issue was I was doing a double map, but rendering the second array. I had to change my key from the parent map id, to the rendered map id so React could detect a change.
results.map((product) => {
return product.variants.map((variant) => (
<div
key={variant.id} <-- changed from product.id to variant.id
>
<div>
{product.title} - {variant.title}
</div>
<div className='font-weight-light'>{variant.sku}</div>
</div>
));
})
Upvotes: 1
Reputation: 41
The other answers are correct, too.
Try using a unique value in the key, but the main problem is that TempArray = array
assigns both variables to the same reference. Because of this, when React tries to compare array
with tempArray
they would be the same value, and this won't trigger a re-render.
To effectively make a copy of an array, try tempArray = [...array]
to avoid making unwanted changes in the original array.
Upvotes: 4
Reputation: 1
TLDR: Try updateArray(tempArr.slice(0))
I struggled with the same problem for quiet some time and the answers did not solve my problem.
If you use modifiedState.slice(0) a duplicate of the prepended object is created, then use setState(modifiedState.slice(0)) or in your case updateArray(tempArr.slice(0)). This forces .map-Operation to rerender.
Upvotes: 0
Reputation: 5107
It's because you're using the index of the elements in the array as the key. React uses key
to decide which elements to rerender; because your keys are always in the same order, React won't update anything. Try:
{array.map((value) => {
return (
<div
style={{ height: `${value * 2}px` }}
className="array-bar"
key={value}
></div>
);
})}
See https://reactjs.org/docs/lists-and-keys.html#keys for more, specifically the following:
We don’t recommend using indexes for keys if the order of items may change. This can negatively impact performance and may cause issues with component state. Check out Robin Pokorny’s article for an in-depth explanation on the negative impacts of using an index as a key. If you choose not to assign an explicit key to list items then React will default to using indexes as keys.
Upvotes: 30