react-beautiful-dnd icon indicating copy to clipboard operation
react-beautiful-dnd copied to clipboard

> Invariant failed: Draggable[id: 8]: Unable to find drag handle

Open 1pauling opened this issue 5 years ago • 50 comments

Please head to the @atlaskit/tree issue tracker.

We do not track @atlaskit/tree issues in the react-beautiful-dnd project

1pauling avatar Feb 20 '20 15:02 1pauling

Screen Shot 2020-02-20 at 10 33 13

1pauling avatar Feb 20 '20 15:02 1pauling

I have followed your video tutorials closely, ensured that draggableId is a string and mapped items get a key. I'm still unable to get dnd to function on page load. However, after making changes to my code and app auto saves, the dnd works as I intended.

1pauling avatar Feb 20 '20 15:02 1pauling

Hi there!

Thanks for raising this issue. Can you please create a standalone example on codesandbox.io using our boilerplate: https://codesandbox.io/s/k260nyxq9v

Without a standalone example, we will not be able to action this one

Cheers!

alexreardon avatar Feb 20 '20 22:02 alexreardon

Hi Alex,

Thank you for getting back to me. I tried replicating my code in your boilerplate and it works just fine. So I don't know how else to show you an example of the problem.

But, one thing I noticed when inspecting the draggable object is the data-rbd-draggable-context-id doesn't start from 0 and every time I refresh the page, the value increases.

Might you have an idea what could be causing the problem?

1pauling avatar Feb 25 '20 15:02 1pauling

@rxannelow If you use SSR i try to use

import { resetServerContext } from "react-beautiful-dnd"




resetServerContext()

and it work!!! :)

cherseryhomedev2 avatar Mar 16 '20 07:03 cherseryhomedev2

+1 here

<DragDropContext onDragEnd={ this.onDragEnd.bind(this) }>
      <Droppable droppableId="images" >
        {
          (provided) => (
            <div ref={provided.innerRef } {...provided.droppableProps} >
            {this.state.images.map( this.image.bind(this) )}
            </div>
          )
        }
      </Droppable>
</DragDropContext>
<Draggable key={ el.id } draggableId={ el.id } index={ idx }>
      {
        ( provided, snapshot ) => {
          return <Box  overflow="hidden" rounded="md" borderWidth="1px" cursor="pointer" {...provided.draggableProps} {...provided.draggableProps} ref={provided.innerRef}>
                <Image src={ data.image } alt={ idx } objectFit="cover" height="175px" width="100%" />
          </Box>
        }
      }
</Draggable>
Screen Shot 2020-04-23 at 11 16 32 AM

samullman avatar Apr 23 '20 18:04 samullman

@samullman you use twice draggableProps and so dragHandleProps are missing

{...provided.draggableProps} {...provided.draggableProps} => {...provided.draggableProps} {...provided.dragHandleProps}

jojkos avatar May 28 '20 10:05 jojkos

same issue here @alexreardon

mina4gerges avatar Jun 22 '20 13:06 mina4gerges

@samullman Spread rest props to the top element of the Box component.

Example

import * as React from "react";

function Box(props) {
  const { overflow, rounded, borderWidth, cursor, ...rest } = props;
  // ... some code

  return <div {...rest}>....</div>
}

rest props is provided.draggableProps and provided.dragHandleProps.

this issue reason is not found dragable props element.

@mina4gerges

kkak10 avatar Jul 23 '20 12:07 kkak10

@rxannelow check my comment.

kkak10 avatar Jul 28 '20 12:07 kkak10

Having this same issue in a Chrome Extension environment. Have it working identically in a Sandbox, but getting same error posted by @samullman above, except I do have the correct props spread on each.

All the items are rendering, but no action when trying to drag anything.

Any reason it may not work in a Chrome Extension? Have inspected the elements and when compared to the demo project they have all the same props on both the Droppable and Draggable elements.

im-thom avatar Sep 16 '20 11:09 im-thom

I'm facing the same issue but testing with enzyme. image

This are my components: (I removed some props to only show the ones that belongs to the library)

<DragDropContext onDragEnd={this.onDragEndHandle}>
  <Droppable droppableId="1-dpb">
    {(provided) => (
      <CustomizedTemplate  provided={provided}  />
    )}
  </Droppable>
</DragDropContext>
import React from 'react';
// Components
import BannerExpand from '../../../organism/BannerExpand';

const Customized = ({ provided }) => (
  <div ref={provided.innerRef} {...provided.droppableProps} >
    {campaigns.map((campaign, index) => (
      <BannerExpand
        position={campaign.id}
        index={index}
      />
    ))}
    {provided.placeholder}
  </div>
);

export default Customized;
/* eslint-disable react/jsx-one-expression-per-line */
import React from 'react';
import { Draggable } from 'react-beautiful-dnd';

const BannerExpand = ({position, index}) => (
    <Draggable draggableId={`${position}-dbl`} index={index}>
      {(provided) => (
        <div
          {...provided.draggableProps}
          {...provided.dragHandleProps}
          ref={provided.innerRef}
        >
          Drag me !
        </div>
      
    </Draggable>
  );
};

export default BannerExpand;

I debug the component with debug() and i got this: image

I get the message from above if I'm using with enzyme with act for async events inside the useEffect hook or the componentDidMount function:

let wrapper = null;

await act(async () => {
  wrapper = mount(<Component />);
});

If i change the code to a sync version, it works ok but I'm still getting the errors from the first image:

let wrapper = mount(<Component />);

DracotMolver avatar Sep 21 '20 18:09 DracotMolver

I have the same issue using FlexLayout after opening a new window it throws those errors

index.js:1 react-beautiful-dndA setup problem was encountered.> Invariant failed: Draggable[id: item-1]: Unable to find drag handle

and here is my code

import React, { useEffect, useState } from 'react';
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';

export default function App() {

  // a little function to help us with reordering the result
  const reorder = (list, startIndex, endIndex) => {
    const result = Array.from(list);
    const [removed] = result.splice(startIndex, 1);
    result.splice(endIndex, 0, removed);
    return result;
  };

  // const grid = 8;

  const getItemStyle = (isDragging, draggableStyle) => ({
    // some basic styles to make the items look a bit nicer
    userSelect: 'none',
    padding: 10,
    margin: `0 0 8px 0`,

    // change background colour if dragging
    background: isDragging ? 'lightgreen' : 'grey',

    // styles we need to apply on draggables
    ...draggableStyle,
  });

  const getListStyle = (isDraggingOver) => ({
    background: isDraggingOver ? 'lightblue' : 'lightgrey',
    padding: 8,
    width: 250,
  });
  const [stateItems, setStateItems] = useState([
    {
      id: `item-1`,
      content: `item $1`,
    },
    {
      id: `item-2`,
      content: `item $2`,
    },
    {
      id: `item-3`,
      content: `item $3`,
    },
    {
      id: `item-4`,
      content: `item $4`,
    },
  ]);


  const onDragEnd = (result) => {
    // dropped outside the list
    if (!result.destination) {
      return;
    }
    const items = reorder(
      stateItems,
      result.source.index,
      result.destination.index
    );
    setStateItems(items);
  };

  // Normally you would want to split things out into separate components.
  // But in this example everything is just done in one place for simplicity
  return (
    <DragDropContext onDragEnd={onDragEnd}>
      <Droppable droppableId="droppable">
        {(provided, snapshot) => (
          <div
            {...provided.droppableProps}
            {...provided.dragHandleProps}
            ref={provided.innerRef}
            style={getListStyle(snapshot.isDraggingOver)}
          >
            {stateItems.map((item, index) => (
              <Draggable key={item.id} draggableId={item.id} index={index}>
                {(provided, snapshot) => (
                  <div
                    ref={provided.innerRef}
                    {...provided.draggableProps}
                    {...provided.dragHandleProps}
                    style={getItemStyle(
                      snapshot.isDragging,
                      provided.draggableProps.style
                    )}
                  >
                    {item.content}
                  </div>
                )}
              </Draggable>
            ))}
            {provided.placeholder}
          </div>
        )}
      </Droppable>
    </DragDropContext>
  );
}


IMM9O avatar Oct 04 '20 14:10 IMM9O

I created a sandbox link to demonstrate my problem pleas check

https://toqkl.csb.app/

IMM9O avatar Oct 04 '20 15:10 IMM9O

I have another example using react-new-window it is not working check codesandbox link https://q5mf6.csb.app/

IMM9O avatar Oct 05 '20 16:10 IMM9O

I have this issue but I'll attempt to debug it, the same code works fine in the storybook for the project when I copy paste it. So seems it might be happening when fired at a certain lifecycle point.

jcampbell05 avatar Oct 29 '20 23:10 jcampbell05

It appears this happens when the component this is contained within takes a while to mount the items even if they are mounted later.

The draggable component runs and tries to read the ref for the DOM which hasn't been created yet.

jcampbell05 avatar Oct 29 '20 23:10 jcampbell05

Solved

  • Note : I used NextJS for React I was getting the same error in development and production as well.

It occurs because DOM and window is not loaded before that our component gets loaded.

Here's a solution :

  • Make Sure your window object is ready before your component loads up.
  • Here's code that I have implemented
import dynamic from "next/dynamic";
const Column = dynamic(import("./Column/Column"));
function ScrumBoard() {
    const [winReady, setwinReady] = useState(false);
    useEffect(() => {
        setwinReady(true);
    }, []);
    return (
        <div className="pl-4 pr-4 pt-3">
           ` <h5>Frontend Board</h5>`
            {winReady ? <Column /> : null}
        </div>
    );
}
export default ScrumBoard;

saurabhburade avatar Nov 29 '20 09:11 saurabhburade

You can check if the Drag Handle is being rendered or not. I faced this issue when my drag handle was being conditionally rendered. If it doesn't get rendered, this warning will be displayed. Something like this...

<div>
      {someCondition && (
           <div>
               <DragIndicatorIcon {...provided.dragHandleProps}/>
           </div>
      )}
</div>

In this case move {...provided.dragHandleProps} outside the condition

shreygeekyants avatar Feb 17 '21 06:02 shreygeekyants

@rxannelow If you use SSR i try to use

import { resetServerContext } from "react-beautiful-dnd"




resetServerContext()

and it work!!! :)

Dude delete this comment, this is wrong what you said here, resetting the context will lead to a lot of strange issues, for those that are visiting this issue in the feature, DO NOT DO THIS.

@saurabhburade 's comment is the one that is the right one, you need to load window before loading RBD since it uses that object. This issue would mostly occur if you're using this library in server-sided rendering, like on NextJS. I was using NextJS, got this same issue, thought @cherseryhomedev2 was right, but she wasn't.

Jaagrav avatar Jul 26 '21 23:07 Jaagrav

For anyone else dealing with this who's ruled out SSR or conditional rendering as their problem:

Because rbdnd queries the dom in a useEffect when mounting the DragDropContext component, you must have your drag handles flushed into the document before the effect runs, that way the call to document.querySelectorAll can find them.

The cause for me was rendering into a node that wasn't inserted into the document until after the useEffect in DragDropContext runs. The tree was rendered into a portal, and my component inserted the dom node into the body in a useEffect call, but this effect was flushed after the one created by DragDropContext, so the DOM node containing all the drag handles wasn't in the document yet.

The fix I used with was to switch from useEffect to useLayoutEffect, but two pass rendering would have worked as well

// Before:
function MyComponent() {
  const wrapperRef = useRef(null);
  if (!wrapperRef.current) wrapperRef.current = document.createElement('div');

  // This effect runs too late so the node isn't in the document by the time rbdnd calls `document.querySelector`
  useEffect(() => {
    document.body.append(wrapperRef.current);
    wrapperRef.current.focus();
    return () => wrapperRef.current.remove();
  }, []);

  return createPortal(..., wrapperRef.current);
}

// After:
function MyComponent() {
  const wrapperRef = useRef(null);
  if (!wrapperRef.current) wrapperRef.current = document.createElement('div');

  // Now the effect will run when flushing the rest of the reconciliation effects to the dom
  // (which makes more sense for what it's doing), and before the effect in rbdnd
  useLayoutEffect(() => {
    document.body.append(wrapperRef.current);
    wrapperRef.current.focus();
    return () => wrapperRef.current.remove();
  }, []);

  return createPortal(..., wrapperRef.current);
}

RusmiselE-lattice avatar Oct 04 '21 18:10 RusmiselE-lattice

Solved

  • Note : I used NextJS for React I was getting the same error in development and production as well.

It occurs because DOM and window is not loaded before that our component gets loaded.

Here's a solution :

  • Make Sure your window object is ready before your component loads up.
  • Here's code that I have implemented
import dynamic from "next/dynamic";
const Column = dynamic(import("./Column/Column"));
function ScrumBoard() {
    const [winReady, setwinReady] = useState(false);
    useEffect(() => {
        setwinReady(true);
    }, []);
    return (
        <div className="pl-4 pr-4 pt-3">
           ` <h5>Frontend Board</h5>`
            {winReady ? <Column /> : null}
        </div>
    );
}
export default ScrumBoard;

this fixed my problem, and it seems to be the easiest solution to start with. it may not be the perfect solution for all cases, but I love the approach to see the main cause of the problem.

djmin43 avatar Nov 24 '21 01:11 djmin43

Having this same issue in a Chrome Extension environment. Have it working identically in a Sandbox, but getting same error posted by @samullman above, except I do have the correct props spread on each.

All the items are rendering, but no action when trying to drag anything.

Any reason it may not work in a Chrome Extension? Have inspected the elements and when compared to the demo project they have all the same props on both the Droppable and Draggable elements.

@im-thom I have the same problem in my extension. Did you know how to fix it?

pavlogavryliuk avatar Feb 16 '22 19:02 pavlogavryliuk

Solved

  • Note : I used NextJS for React I was getting the same error in development and production as well.

It occurs because DOM and window is not loaded before that our component gets loaded.

Here's a solution :

  • Make Sure your window object is ready before your component loads up.
  • Here's code that I have implemented
import dynamic from "next/dynamic";
const Column = dynamic(import("./Column/Column"));
function ScrumBoard() {
    const [winReady, setwinReady] = useState(false);
    useEffect(() => {
        setwinReady(true);
    }, []);
    return (
        <div className="pl-4 pr-4 pt-3">
           ` <h5>Frontend Board</h5>`
            {winReady ? <Column /> : null}
        </div>
    );
}
export default ScrumBoard;

Problem is still here https://i.imgur.com/3PuQkR8.png

Here is my wrapper https://i.imgur.com/ltlaQbH.png Here is my component https://i.imgur.com/KWVQj8H.png

As you can see window is already defined when im trying to use DnD, but errors and warnings are still here. Any suggestions?

Revolt9k avatar Apr 08 '22 09:04 Revolt9k

So I followed the guide in the on egg head and I attempted to write 
this in functions instead of class,
 i'm still very new to this and I was wondering if anyone could point out what I'm doing wrong, here are  my files
import React, { useState } from 'react';
import ReactDOM from 'react-dom/client';
import '@atlaskit/css-reset';
import initialData from './initial-data';
import Column from './column';
import { DragDropContext } from 'react-beautiful-dnd';


export default function App () {
  const [state, setState] = useState(initialData);
  const onDragEnd = () => {
    // to do
  }
 
  return (
    <DragDropContext
    //only required prop but there is also onDragStart and onDragUpdate
    onDragEnd={onDragEnd}
    >
      {state.columnOrder.map((columnId) => {
      //pulling that column out of state
      const column = state.columns[columnId];
      // and we want the task associated with that column
      const tasks = column.taskIds.map(taskId => state.tasks[taskId]);
      console.log(tasks);

      return <Column key={column.id} column={column} tasks={tasks}/>;
    })}
    </DragDropContext>
  );
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);`

import React from 'react';
import styled from 'styled-components';
import { Droppable } from 'react-beautiful-dnd';
import Task from './task';

const Container = styled.div`
    margin: 8px;
    border: 1px solid lightgrey;
    border-radius: 2px;
`;
const Title = styled.h3`
    padding: 8px;    
`;
const TaskList = styled.div`
    padding: 8px;
`;

export default function Column (props) {
    return (
        <Container>
            <Title>{props.column.title}</Title>
            <Droppable droppableId={props.column.id}>
                {(provided) => (
                    <TaskList
                    ref={provided.innerRef}
                    {...provided.droppableProps}
                    >
                    {props.tasks.map((task, index) => <Task key={task.id} task={task} index={index} />)}
                    {provided.placeholder}
                    </TaskList>
                )}
            </Droppable>
        </Container>
    );
}
`import React from 'react';
import styled from 'styled-components';
import { Draggable } from 'react-beautiful-dnd';

const Container = styled.div`
    border: 1px solid lightgrey;
    border-radius: 2px
    padding: 8px;
    margin-bottom: 8px;
`;

export default function Task (props) {
    return(
        //draggable had two required props, 
        // just like the droppable a draggable expects its child to be a function 
        <Draggable draggableId={props.task.id} index={props.index}>
            {(provided) => (
                <Container
                {...console.log(props.task.id)}
                ref={provided.innerRef}
                {...console.log(provided)}
                    {...provided.draggableProps}
                    {...provided.dragHandleProps}
                >
                {props.task.content}
                </Container>        
            )}
        </Draggable>
    );
}

const initialData = {
    tasks: {
        'task-1': { id: 'task-1', content: 'Take out the trash' },
        'task-2': { id: 'task-2', content: 'Clean up the office' },
        'task-3': { id: 'task-3', content: 'Finish your project' },
        'task-4': { id: 'task-4', content: 'Meal prep this afternoon' }
    },
    columns: {
        'column-1': {
            id: 'column-1',
            title: 'To do',
            taskIds: ['task-1', 'task-2', 'task-3', 'task-4'],
        },
    },
    columnOrder: ['column-1'],
};

export default initialData;

Milagro233 avatar Apr 23 '22 22:04 Milagro233

I also had this problem.

It's because I was lazily mounting sub-components of the Draggable when they scroll into view. Even though initially some of these components are immediately mounted because they are at the top of the document, it seems to take some time and during that time there's no drag handles so I get the error.

To fix it I had to add some code to ensure there's always a drag handle rendered when the Draggable component is first mounted.

I wouldn't have seen this error at all if it wasn't for the slightly delayed mounting of the lazy components.

ashleydavis avatar May 20 '22 06:05 ashleydavis

So you added a useEffect? what code should I include in my useEffect to ensure they always have a dragHandle

Milagro233 avatar May 20 '22 12:05 Milagro233

ok so I have no Idea why this works, but I just added to console. logs in a useEffect on both, the draggable and the droppable component, and now I can drag things. useEffect(() => { console.log(props.column) }, []) useEffect(() => { console.log(props.task) }, []);

Milagro233 avatar May 20 '22 12:05 Milagro233

No @Milagro233 my project predates hooks!

Here is the render function of my lazy loaded component:

image

You can see the code that calls renderPlaceholder? This renders the placeholder before the real component is mounted. At the next level up both the placeholder and the main component contain the drag handles required so that react-beautiful-dnd doesn't give the error messages.

Here's the code for the Lazy component: https://github.com/data-forge-notebook/editor-core/blob/main/src/lib/lazy.tsx

The reason I use this component is so that I have 100s of expensive components in page but the expensive part of them doesn't get mounted until the component is scrolled into view.

Hopefully that all makes sense ;)

ashleydavis avatar May 21 '22 01:05 ashleydavis

Below helped :

  1. Maintaining a flag (state)
  2. Changing its value on rendering
  3. Conditionally rendering <Draggable> (Wrapping with the flag)

Below is the code for reference : 1) const [showUIElements, setShowUIElements] = React.useState(false);

2)

useEffect(() => {
  setShowUIElements(true);
 }, []);

3)

return (
   showUIElements && (
     <Droppable
       droppableId={listId}
       type={listType}
       direction="horizontal"
     >
       {dropProvided => (
         <div {...dropProvided.droppableProps}>
           <div>
             <div className="characterso" ref={dropProvided.innerRef}>
               <div className="title" >
                 {listId}
               </div>
               <div className="break"></div><br />
               <div style={{ display: "flex" }} >

                 {characters.map(({ id, name, thumb }, index) => (

                   <Draggable key={id} draggableId={id} index={index}>
                     {dragProvided => (
                       <div className="characterso"
                         {...dragProvided.dragHandleProps}
                         {...dragProvided.draggableProps}
                         ref={dragProvided.innerRef}
                       >

                         <div className="characters-thumb">
                           <img style={{ display: "inline-block" }} width="400" height="400" src={thumb} alt={`${name} Thumb`} />
                         </div>
                         <div className="charactersName">{name}</div>
                       </div>
                     )}
                   </Draggable>
                 ))}
                 {dropProvided.placeholder}
               </div>
             </div>
           </div>
         </div>
       )}
     </Droppable>
   )

shailjaparihar avatar May 25 '22 15:05 shailjaparihar