react: Bug ?: Weird reconciliation result on list elements from array with same length as the list.

React version: 16.13.1

The thing turns out to be possible something else other than what it looks like when I first post this. So I’ve changed the title to better reflect the issue and updated some comment to the example code below. Nothing else were modified just to keep a clear record how things are discovered.

Steps To Reproduce

  1. Run this code and check in browser what elements are destroyed or reused.
import React, { useState } from 'react';
import ReactDOM from 'react-dom';

const num = [3, 4, 5, 6]; // array serving content for list elements
const loop = [1, 2, 3, 4]; // array to be mapped to generate list

const RollingNums = () => {
  const [cur, setCur] = React.useState(0); // control number rolling

  return (
    <div className="problem">
      <ul>
        {loop.map((value, index) => { // list item counts are fixed to loop.length
          // some simple CS to get continued list item
          let idx = index + cur; 
          idx =
            idx >= 0
              ? idx % num.length
              : ((idx % num.length) + num.length) % num.length;
          const target = num[idx].toString(); // get content from array num for current list element
          return (
            <li key={target} className={target}>
              {target}
            </li>
          );
        })}
      </ul>
      <div className="control">
        <button onClick={() => setCur((prev) => prev - 1)}>-</button>
        <span>{cur}</span>
        <button onClick={() => setCur((prev) => prev + 1)}>+</button>
      </div>
    </div>
  );
};

ReactDOM.render(<RollingNums />, document.querySelector('#root'));

Example on codepen.

The current behavior

Clicking on + destroys the first item and recreates it as the last item, while reusing all others. Clicking on - reuses first item (the last item from last render) and destroys/recreates all others. This only happens if num and loop have same length. If num has more items than loop does, items are destroyed/reused as expected.

The expected behavior

All items should be reused instead of being destroyed.

Although it looks perfectly fine in this simple app, everything still work. However, all class driven transitions mess up due to item destroy/recreate, and children elements underneath <li> will reinitialize (e.g. <img> reload) due to the same reason.

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 2
  • Comments: 20 (3 by maintainers)

Most upvoted comments

Closing this issue after a prolonged period of inactivity. If this issue is still present in the latest release, please create a new issue with up-to-date information. Thank you!