react-beautiful-dnd
react-beautiful-dnd copied to clipboard
> Invariant failed: Draggable[id: 8]: Unable to find drag handle
Please head to the @atlaskit/tree
issue tracker.
We do not track @atlaskit/tree
issues in the react-beautiful-dnd
project
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.
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!
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?
@rxannelow If you use SSR i try to use
import { resetServerContext } from "react-beautiful-dnd"
resetServerContext()
and it work!!! :)
+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>

@samullman you use twice draggableProps and so dragHandleProps are missing
{...provided.draggableProps} {...provided.draggableProps}
=>
{...provided.draggableProps} {...provided.dragHandleProps}
same issue here @alexreardon
@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
@rxannelow check my comment.
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.
I'm facing the same issue but testing with enzyme.
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:
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 />);
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>
);
}
I created a sandbox link to demonstrate my problem pleas check
https://toqkl.csb.app/
I have another example using react-new-window it is not working check codesandbox link https://q5mf6.csb.app/
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.
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.
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;
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
@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.
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);
}
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.
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?
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?
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;
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.
So you added a useEffect? what code should I include in my useEffect to ensure they always have a dragHandle
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) }, []);
No @Milagro233 my project predates hooks!
Here is the render function of my lazy loaded component:
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 ;)
Below helped :
- Maintaining a flag (state)
- Changing its value on rendering
- 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>
)