clauderic / dnd-kit

The modern, lightweight, performant, accessible and extensible drag & drop toolkit for React.
http://dndkit.com
MIT License
13.31k stars 658 forks source link

'Maximum update depth exceeded' error with Sortable #900

Open giovannipds opened 2 years ago

giovannipds commented 2 years ago

I'm just reopening this issue https://github.com/clauderic/dnd-kit/issues/496, people has been posting there, so prolly the issue is still around (I have that in the project that I'm working on it too lol).

rharfordhacks commented 2 years ago

Has there been any update on this issue?

hermantolim commented 1 year ago

also got this issue, mine fine in firefox, but fail in chrome

eashish93 commented 12 months ago

Edit: I found the solution. In onDragOver, any state update you make, wrap it inside startTransition or requestAnimationFrame. startTransition is native way by react though

So, here's my demo. In my case, it's happening because of mixed height items. When all items are of equal height, there's no error.

[

https://github.com/clauderic/dnd-kit/assets/13780952/3dd5e5b3-901f-4344-9928-9c35cc9c114d

](url)

phongnguyenhh1996 commented 11 months ago

same issue +1

NoamBasha commented 11 months ago

I had the same problem and I fixed it by using a composition of existing collision-detection algorithms:

import { closestCorners, closestCenter, pointerWithin } from "@dnd-kit/core";

export function customCollisionDetectionAlgorithm(args) {
    const closestCornersCollisions = closestCorners(args);
    const closestCenterCollisions = closestCenter(args);
    const pointerWithinCollisions = pointerWithin(args);

    if (
        closestCornersCollisions.length > 0 &&
        closestCenterCollisions.length > 0 &&
        pointerWithinCollisions.length > 0
    ) {
        return pointerWithinCollisions;
    }

    return null;
}

And then of course used it in the DndContext:

<DndContext
    onDragStart={onDragStart}
    onDragEnd={onDragEnd}
    onDragOver={onDragOver}
    sensors={sensors}
    collisionDetection={customCollisionDetectionAlgorithm}
>
thomasbritton commented 10 months ago

I am getting the same issue, I have tried the suggestions above but no joy.

Here is my code:

const SortableList = ({ items, onSortEnd, hasDragHandle }: SortableListProps) => {
    const [activeId, setActiveId] = useState<UniqueIdentifier | null>(null);
    const getIndex = (id: UniqueIdentifier) => items.indexOf(+id);
    const sensors = useSensors(
      useSensor(MouseSensor, {
        // Require the mouse to move by 10 pixels before activating.
        // Slight distance prevents sortable logic messing with
        // interactive elements in the handler toolbar component.
        activationConstraint: {
          distance: 10,
        },
      }),
      useSensor(TouchSensor, {
        // Press delay of 250ms, with tolerance of 5px of movement.
        activationConstraint: {
          delay: 250,
          tolerance: 5,
        },
      }),
    );

    const previewItems = useMemo(
      () => items.map((item) => previewedResultsGrid[item.index]),
      [items],
    );

    return (
      <DndContext
        sensors={sensors}
        autoScroll={false}
        onDragStart={({ active }) => {
          if (active) {
            setActiveId(active.id);
          }
        }}
        onDragEnd={({ active, over }) => {
          if (over && active.id !== over.id) {
            onSortEnd({
              oldIndex: getIndex(active.id),
              newIndex: getIndex(over.id),
            });
          }
          setActiveId(null);
        }}
        onDragCancel={() => setActiveId(null)}
      >
        <SortableContext items={previewItems} strategy={rectSwappingStrategy}>
          <div
            ref={parentRef}
            className={cx(
              stl`relative select-none overflow-x-hidden overflow-y-scroll`,
              'NewRuleResult_container',
              currentRule.hasConditions && currentRule.hasConsequences && 'with-confirmBar',
            )}
            style={{
              ...containerStyle,
              width: layout.width,
              height: layout.height,
              paddingBottom: currentRule.hasConditions && currentRule.hasConsequences ? 85 : 0,
            }}
          >
            <ul
              className={stl`relative overflow-hidden`}
              style={{
                height: totalSize + DETAILS_EXPANSION_HEIGHT,
                width: layout.width - containerLeftPadding - containerRightPadding,
              }}
            >
              {virtualItems.map((item) => {
                const columnWidth = layout.itemWidth + layout.gutterSize;
                const sharedItemStyles: CSSProperties = {
                  position: 'absolute',
                  top: item.start,
                  height: rowHeight,
                  ...(layout.mode === 'grid'
                    ? {
                        width: columnWidth,
                        paddingTop: MULTISELECT_CHECKBOX_SIZE / 2,
                      }
                    : { width: layout.itemWidth, padding: MULTISELECT_CHECKBOX_SIZE / 2 }),
                };

                return (
                  <Fragment key={`${layout.mode}-${item.index}`}>
                    {previewedResultsGrid[item.index].map((result, columnIndex) => {
                      return (
                        <li
                          key={result.objectID}
                          style={{ ...sharedItemStyles, left: columnWidth * columnIndex }}
                        >
                          hello
                        </li>
                      );
                    })}
                    ;
                  </Fragment>
                );
              })}
              ;
            </ul>
          </div>
        </SortableContext>
      </DndContext>
    );
  };

  return <SortableList items={virtualItems} onSortEnd={onSortEnd} hasDragHandle={true} />;
};

export const SortableResultsItems = SortableContainer(observer(ResultsItems));
HenrikZabel commented 9 months ago

@thomasbritton Try following:

...
onDragStart={({ active }) => {
  if (active) {
    setTimeout(() => setActiveId(active.id), 0)
  }
}}
...
bhutoria commented 8 months ago

the solution by @HenrikZabel works. However, I implemented it in OnDragOver to update the array at a timeout of 5 ms. the issue is clearly happening with ondragover.

Dentling commented 8 months ago

I had the same issue although I did not use onDragOver. For me the problem was that i used the PointerSensor instead of the MouseSensor

const sensors = useSensors(useSensor(MouseSensor)); <----- This fixed it

<DndContext sensors={sensors} onDragEnd={handleDragEnd} collisionDetection={closestCenter}>
     <SortableContext items={items} strategy={rectSwappingStrategy}>
            {...children}
     </SortableContext>
</DndContext>

So changing the Sensor fixed it for me.

Maybe this helps someone having the same issue.

Lekanjoy commented 7 months ago

I had the same problem and I fixed it by using a composition of existing collision-detection algorithms:

import { closestCorners, closestCenter, pointerWithin } from "@dnd-kit/core";

export function customCollisionDetectionAlgorithm(args) {
    const closestCornersCollisions = closestCorners(args);
    const closestCenterCollisions = closestCenter(args);
    const pointerWithinCollisions = pointerWithin(args);

    if (
        closestCornersCollisions.length > 0 &&
        closestCenterCollisions.length > 0 &&
        pointerWithinCollisions.length > 0
    ) {
        return pointerWithinCollisions;
    }

    return null;
}

And then of course used it in the DndContext:

<DndContext
    onDragStart={onDragStart}
    onDragEnd={onDragEnd}
    onDragOver={onDragOver}
    sensors={sensors}
    collisionDetection={customCollisionDetectionAlgorithm}
>

This also seems to fix the same issue I was facing. Thank you.

timreach commented 6 months ago

So I thought the custom collision detection was fixing it, but it turns out it was not. I instead implemented a 0ms debounced state setter based on this comment and this is now working perfectly.

import {
    DndContext,
    DragOverlay,
    MouseSensor,
    TouchSensor,
    useSensor,
    useSensors,
    type DragEndEvent,
    type DragOverEvent,
    type DragStartEvent
} from "@dnd-kit/core";

import { useDebouncedCallback } from "use-debounce";
// ...

export function Kanban() {
    // ...
    const [items, setItemsUndebounced] = useState<Item[]>(initialItems);
    const setItems = useDebouncedCallback(setItemsUndebounced, 0);
    // ...
    function onDragOver(event: DragOverEvent) {
        // ...
            setItems((items) => {
                // ...
                return arrayMove(items, activeIndex, overIndex);
            });
        // ...
    }
    // ...
}
AmirhoseinHesami commented 6 months ago

Edit: I found the solution. In onDragOver, any state update you make, wrap it inside startTransition or requestAnimationFrame. startTransition is native way by react though

So, here's my demo. In my case, it's happening because of mixed height items. When all items are of equal height, there's no error.

[

Screen.Recording.2023-12-07.at.3.25.11.PM.mov ](url)

thank you so much your solution helped me

rahulthomasdev commented 6 months ago

I had the same error, i was using the Component with useDroppable() hook within the same component that uses it, i just separated the component with useDroppable() hook into a separate file and imported into the main component. Now it works! Hope it would help someone.

usmankhan002011 commented 4 months ago

Wrapping the state update call in a debounce helps solve this issue as well

Alfxjx commented 1 month ago

for anyone meet this problem, I am rendering the sortableItem in a hook, maybe you should useMemo the node to avoid infinite rendering

function useParser() {return {TreeNode}} the TreeNode is wrapped in a useMemo and the code is no error