Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rows with dynamic height overlap when using CellMeasurer #1836

Open
StephenChips opened this issue Apr 24, 2024 · 1 comment
Open

Rows with dynamic height overlap when using CellMeasurer #1836

StephenChips opened this issue Apr 24, 2024 · 1 comment

Comments

@StephenChips
Copy link

Bug Report

I don't know if is a bug or it's just a misuse. If it isn't a bug, please tell me why this happens then close the issue.

Here is the codesandbox contains the code, it is also added at the end of this post:

https://codesandbox.io/p/sandbox/restless-river-5sk2s4

What is the current behavior?

I was following the instruction to write a simple <List /> demo. The list shows some random lorem ipsum sentences and each row has fixed width and dynamic height. As shown in the guide, I should use <CellMeasurer> and CellMeasureCache together to figure out each row's height for the component, so I did. However when I set the defaultHeight to 0, some rows weirdly overlap. It seems their heights were not calculated correctly.

const cellMeasurerCache = new CellMeasurerCache({
    fixedWidth: true,
    defaultHeight: 0
});

If I set the defaultHeight to value larget than 3 (defaultHeight > 3), no row will overlaps anymore.

What is the expected behavior?

No matter what defaultHeight I set, every row's height should be calculated correctly based on it's content's height and none of row
should overlaps with the previous or next one.

Which versions of React and react-virtualized, and which browser / OS are affected by this issue? Did this work in previous versions of react-virtualized?

Browser Chrome 124.0.6367.61
OS Windows 11
React 18.2.0
React DOM 18.2.0
react-virtualized 9.22.5

The code

import { List, CellMeasurer, CellMeasurerCache } from "react-virtualized"

const cellMeasurerCache = new CellMeasurerCache({
    fixedWidth: true,
    defaultHeight: 0
});

const list = [
    "Qui amet laboris esse id nisi labore adipisicing dolore magna eu cillum eiusmod. Ipsum dolore commodo ea fugiat est anim Lorem deserunt officia adipisicing laboris officia esse mollit. Magna ullamco aliqua irure commodo cupidatat incididunt anim nisi Lorem anim commodo incididunt.",
    "Ad pariatur excepteur duis ipsum sint elit ipsum. Magna culpa quis do dolor non duis elit deserunt fugiat est. Aute irure nisi labore ullamco ut qui pariatur aute excepteur Lorem tempor.",
    "Excepteur excepteur nostrud sunt ut laboris. Irure nostrud laborum esse eiusmod occaecat ullamco ut id sit. Non quis occaecat adipisicing irure duis in tempor.",
    "Culpa amet nostrud adipisicing aliquip culpa est culpa. Incididunt qui exercitation commodo pariatur anim exercitation consequat magna duis sint fugiat et enim ea. Ipsum qui nulla pariatur laboris aliquip.",
    "Ullamco excepteur non aliqua id voluptate. Labore adipisicing nisi magna reprehenderit anim fugiat. Quis in mollit nulla do nisi et est mollit nulla aute anim amet labore amet.",
    "Excepteur sit amet id cillum quis officia laborum ea commodo eu do qui sunt. Exercitation ex ullamco tempor enim qui dolore. Dolor cupidatat ex dolore deserunt exercitation in incididunt cillum.",
    "Est pariatur adipisicing mollit non aliquip irure occaecat officia laborum in ex enim. Officia enim sunt anim excepteur id non laborum nulla consectetur ex in do aute cillum. Et consectetur proident non et voluptate aliquip laborum aute dolore sunt.",
    "Reprehenderit nisi ipsum pariatur pariatur mollit ipsum elit et pariatur amet ad commodo dolore. Sunt fugiat velit velit cupidatat sunt irure sint nostrud non amet sint dolore eu cupidatat. Fugiat magna Lorem commodo dolore labore magna sint veniam minim.",
    "Fugiat dolor labore exercitation deserunt proident nulla magna laborum et non aliquip Lorem quis. Laborum consequat duis eiusmod ipsum aute quis labore. Labore voluptate fugiat voluptate velit Lorem duis mollit.",
    "Tempor pariatur do ipsum adipisicing excepteur id deserunt mollit Lorem minim ea qui pariatur. Ad nisi culpa labore anim dolor exercitation do incididunt minim quis in tempor ex aliquip. Minim nisi reprehenderit dolor nisi non aliqua consectetur.",
    "Sit non est do incididunt amet do aliqua est. Ullamco tempor laboris proident amet nisi esse labore occaecat amet dolore cupidatat aute mollit. Magna sint ex aliquip mollit laborum elit nostrud in voluptate.",
    "Sit Lorem do eiusmod aliqua nisi mollit aliqua magna Lorem amet. Ipsum consectetur excepteur tempor fugiat irure cupidatat id esse esse dolore ullamco deserunt mollit. Lorem sint non reprehenderit labore non minim reprehenderit veniam id deserunt.",
    "Laboris laboris occaecat sit id voluptate tempor nulla et. Irure eiusmod mollit excepteur laboris incididunt nulla ea ipsum consectetur irure ex ipsum nulla. Dolore tempor ipsum anim elit deserunt culpa voluptate.",
    "Commodo reprehenderit magna amet eiusmod commodo est occaecat veniam exercitation sit dolor laborum voluptate sunt. Id nulla magna nostrud id excepteur excepteur minim commodo duis amet ea nisi aute. Eiusmod incididunt exercitation reprehenderit culpa officia nulla pariatur sunt magna excepteur quis eu mollit.",
    "Officia eu elit cillum cupidatat commodo dolor. Occaecat irure minim irure nulla magna reprehenderit voluptate. Consectetur ut enim anim qui magna dolore do cupidatat sint sunt et consequat.",
    "Adipisicing veniam laboris excepteur ipsum velit veniam duis consectetur sint duis culpa reprehenderit Lorem. Elit laborum occaecat commodo ad aliqua aliqua et non adipisicing exercitation. Id reprehenderit et excepteur labore quis aliqua.",
    "Dolor minim et in commodo nulla in aliquip. Aute occaecat occaecat irure voluptate. Consectetur ad exercitation eiusmod irure do deserunt cupidatat proident consequat reprehenderit occaecat enim.",
    "Nisi ipsum commodo esse quis in id anim amet occaecat occaecat cupidatat est Lorem. Sint nostrud adipisicing quis proident in dolor esse laboris ad. Reprehenderit duis laborum nostrud aute in sit excepteur ad culpa nulla sit elit ex commodo.",
    "Cupidatat ea est Lorem aute laborum et reprehenderit sit. Minim dolor adipisicing laboris cupidatat est officia excepteur ea deserunt deserunt adipisicing. Ipsum incididunt tempor nulla laborum ut officia excepteur consectetur quis et commodo nostrud.",
    "Cillum aute nisi sunt qui do irure proident minim nostrud dolore ullamco enim dolor exercitation. Aute ullamco qui est reprehenderit deserunt qui aliqua. Et cupidatat cillum ex enim dolor et eiusmod deserunt sint amet deserunt consequat consectetur.",
    "Ipsum pariatur aliquip laboris id cupidatat mollit id ea commodo officia qui. Dolor laboris officia ad id aute qui. Lorem cupidatat do occaecat aliqua mollit irure ullamco duis proident magna voluptate incididunt veniam consectetur.",
    "Incididunt minim pariatur ex excepteur. Sit est aliquip nisi exercitation reprehenderit. Fugiat ut minim ea anim esse adipisicing.",
    "Consectetur veniam qui non culpa ex cillum id aliquip et consectetur quis velit consequat. Commodo incididunt nulla laborum id magna in et eu veniam cillum mollit. Fugiat proident enim nulla ea et laboris aliqua est excepteur et duis fugiat.",
    "Voluptate incididunt enim irure labore pariatur tempor aute cillum in. Esse eu laboris officia enim quis. Consequat culpa laboris qui voluptate id nisi eiusmod.",
    "Eu ex commodo est enim nulla dolore cillum incididunt qui. Enim amet laborum dolor quis Lorem ullamco amet pariatur elit tempor. Non commodo fugiat elit nisi nisi.",
    "Aliqua dolore ullamco consequat proident veniam labore. Culpa veniam deserunt veniam ad dolor voluptate culpa veniam excepteur do voluptate fugiat. Sint anim officia exercitation ipsum elit dolor magna esse sint.",
    "Ullamco eiusmod aliquip velit nisi proident cillum consequat magna dolore quis aute. Laborum sint et est eiusmod culpa labore. Velit incididunt proident veniam exercitation.",
    "Et culpa dolore ullamco esse adipisicing consectetur. Id culpa sit aliquip sit incididunt aliqua. Quis ut nulla et exercitation commodo ad cillum proident.",
    "Sit adipisicing et aliquip ea veniam incididunt nulla incididunt anim ad ad dolor. Consectetur tempor occaecat deserunt amet dolor aute proident ipsum Lorem occaecat ipsum id. Tempor ullamco commodo veniam minim culpa et fugiat anim sunt fugiat est pariatur.",
    "Mollit veniam in labore laboris occaecat dolore. Aliqua laborum irure qui incididunt consequat sint incididunt ipsum aliquip nisi commodo voluptate occaecat ut. Enim occaecat elit consequat pariatur mollit do ut aliqua irure minim veniam duis amet dolor.",
    "Id qui est in elit exercitation officia excepteur sit sint ullamco. Commodo esse fugiat dolor ipsum nulla mollit cillum cupidatat. Duis officia veniam laborum qui proident laborum labore deserunt nisi non ut Lorem excepteur mollit.",
    "Culpa proident ipsum eiusmod et excepteur id laboris culpa quis irure. Id sint enim deserunt irure voluptate ut minim. Aute veniam labore labore sit adipisicing cupidatat do ex anim cupidatat reprehenderit culpa.",
    "Dolor ad et aliquip ipsum proident. Ut irure eu velit ut proident enim nisi aliquip ex aliqua commodo voluptate dolore reprehenderit. Est esse ad minim laborum enim eiusmod aliqua ullamco est cupidatat ullamco cupidatat laboris nulla.",
    "Quis labore laborum ex dolore veniam consectetur quis. Laboris ipsum aute sit pariatur deserunt laboris ad anim. Nulla esse occaecat nulla mollit occaecat sit.",
];

export default function App() {
    function renderRow({ index, key, parent, style }) {
        return (
            <CellMeasurer
                cache={cellMeasurerCache}
                columnIndex={0}
                key={key}
                parent={parent}
                rowIndex={index}>
                    {({registerChild}) => (
                        <div ref={registerChild} key={key} style={style}>{list[index]}</div>
                    )}
            </CellMeasurer>
        );
    }

    return (
        <div style={{fontSize: "18px"}}>
            <List height={1000} rowHeight={cellMeasurerCache.rowHeight} width={1000} rowCount={300} rowRenderer={renderRow}></List>
        </div>
    )
}

The overlapped columns

image
@StephenChips StephenChips changed the title Columns with dynamic height overlap when using CellMeasurer Rows with dynamic height overlap when using CellMeasurer Apr 24, 2024
@DenisLantero
Copy link

DenisLantero commented May 6, 2024

Hi, I have a similar issue.

I am using react-virtualized and react-beautiful-dnd in order to make a kanban board with virtualized columns.
The issue that I'm encountering is that the row measurements are not always correct, even if I set defaultHeight to something bigger than 0 (if I set it to 0, the heights don't get calculated correctly, as @StephenChips said).

Here's a video of the issue:
https://github.com/bvaughn/react-virtualized/assets/83024157/239f86a1-a9a5-4aef-9ea7-7d5a05305401

As you can see, at the start, the heights are calculated correctly, but sometimes the cards overlap each other.

I checked if this was an issue with my implementation of react-beautiful-dnd or CellMeasurer, but the issue seems to be that the height of the card changes even if the only thing that changes on the card is the transfrom property (caused by the dnd library), and the measured height put in the style prop passed by the List component is less than the actual height, causing the card under to overlap.

This is the list I use in my Column component:

const cache = new CellMeasurerCache({
  defaultHeight: 250,
  fixedWidth: true,
})
// Clear the cache when the filtered tickets change
useEffect(() => {
  // This is needed otherwise the items height would be wrong
  setTimeout(() => {
    cache.clearAll()
    listInstanceRef.current && listInstanceRef.current.recomputeRowHeights()
  }, 0)
}, [filteredTickets])
 
<List
  height={600} // This should be the height of the viewport
  mode='virtual'
  width={provided.innerRef?.current?.clientWidth || 300}
  deferredMeasurementCache={cache}
  rowHeight={cache.rowHeight}
  rowRenderer={Row}
  rowCount={itemCount}
  tickets={filteredTickets}
  ref={(ref) => {
    // react-virtualized has no way to get the list's ref that I can so
    // So we use the `ReactDOM.findDOMNode(ref)` escape hatch to get the ref
    if (ref) {
    // eslint-disable-next-line react/no-find-dom-node
      const listRef = ReactDOM.findDOMNode(ref)
      if (listRef instanceof HTMLElement) {
        provided.innerRef(listRef)
      }

      listInstanceRef.current = ref
    }
  }}
/>

function Row ({ index, isScrolling, key, parent, style }) {
  const tickets = parent.props.tickets
  const ticket = tickets?.[index]

  if (!ticket) {
    console.log("Ticket doesn't exist, index: ", index)
    return null
  }

  const patchedStyle = {
    ...style,
    top: style.top + 7.5,
    left: style.left,
    height: style.height,
    width: style.width,
  }

  return (
    <CellMeasurer
      cache={cache}
      columnIndex={0}
      key={key}
      rowIndex={index}
      parent={parent}
    >
      {({ measure, registerChild }) => {
        return <Ticket
          key={ticket.id}
          position={index}
          ticket={ticket}
          style={patchedStyle}
          filtered={true}
          showStatus={true}
          measureRef={registerChild}
          measure={measure}
          cache={cache}
        />
      }}
    </CellMeasurer>

  )
}

And this is what I do in my Ticket component:

// Measure the ticket height
useEffect(() => {
  if (measure) measure()
}, [measure, measureRef])

const renderTicket = (provided) => {
    return (
      <div
        key={ticket.id}
        ref={(el) => {
          provided.innerRef(el)
          if (measureRef) measureRef(el)
        }}
        className={classNames(
          'kanban-board__ticket',
          styles.ticket,
          {
            incomplete: ticket.incomplete,
            'activities-completed': ticket.activities_completed,
          }
        )}
        onMouseEnter={handleMouseEnter}
        onMouseLeave={handleMouseLeave}
        {...provided.draggableProps}
        {...provided.dragHandleProps}
        style={{
          ...style,
          ...provided.draggableProps.style,
        }}
      >
        {/* Other code */}
      </div>
    )
  }


  if (isDragPlaceholder) {
    return renderTicket(providedProp)
  }

  return (
    <Draggable
      key={ticket.id}
      draggableId={ticket.id.toString()}
      index={position}
      isDragDisabled={false}
    >
      {(provided) => renderTicket(provided)}
    </Draggable>
  )

Am I doing something wrong, or is there something wrong with how CellMeasurer calculates the height of the element?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants