Adam Jean-Laurent
Adam Jean-Laurent

Reputation: 235

React .map not re-rendering

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

Answers (6)

neeraj minhas
neeraj minhas

Reputation: 11

just do this updateArray([...tempArr]) and see magic

Upvotes: -1

Rob
Rob

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

Alex S
Alex S

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

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

Coni
Coni

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

Hamms
Hamms

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

Related Questions