Hi!
I love how these components work, but now that I'm trying to integrate react-sortable-hoc into my app with my custom components, I'm having issues.
None of the elements in my list are draggable, except for the first one. When I start dragging that element (in fact when I first click it), I see a javascript error in the console:
Uncaught TypeError: Cannot read property 'left' of undefined
at _class.animateNodes (index.js:593)
at _this.handleSortMove (index.js:272)
I stepped into the debugger, and discovered that the method _this.getEdgeOffset(node); is returning undefined. I noticed that this function is recursively calling itself many, many times, because node.parentNode !== this.container. The function getEdgeOffset calls itself for every element all the way up to body, html, and document, until it assigns undefined to _this.offsetEdge. I'm not sure if this is the cause, or related in some way, or just a red herring.
Are my components somehow in the wrong order? Is this related to the "Unknown" component I see in the react debugger?

Any help would be greatly appreciated.
Here's a bit of my code (too much to paste it all in). Note that <Topic> and <TopicSection> are just presentational components which have several DOM elements wrapped in a parent
const SortableList = SortableContainer(({
items,
onToggleSection,
onChangeSectionName,
onSaveSectionName,
onEditSectionName,
onDeleteSection,
onToggleAllTopics,
onAddNewTopic,
onDragSection,
collapsedSection,
collapsedAllTopics,
editMode,
tempSectionName,
editingSectionName,
collapsedDetails,
onToggleDetails,
collapsedTopic,
onToggleTopic,
onDragTopic,
onDeleteTopic,
}) => {
let lastDivider = '';
return (
items.map( (item, idx) => {
switch( item.type ) {
case 'divider':
// don't render sections that have been deleted
if('deleted' in item && item.deleted === 1) {
return '';
}
lastDivider = item.linkid;
return (
onChangeSectionName={onChangeSectionName}
onSaveSectionName={onSaveSectionName}
onEditSectionName={onEditSectionName}
onDeleteSection={onDeleteSection}
onToggleAllTopics={onToggleAllTopics}
onAddNewTopic={onAddNewTopic}
onDragSection={onDragSection}
collapsedSection={collapsedSection}
collapsedAllTopics={collapsedAllTopics}
editMode={editMode}
tempSectionName={tempSectionName}
editingSectionName={editingSectionName}
section={item}
index={idx}
key={item.linkid}
/>
);
case 'topic':
if(!collapsedSection[lastDivider] && !('deleted' in item && item.deleted === 1)) {
return (
<SortableTopic
rootClassName='TopicPlan-topic'
topic={item}
collapsedDetails={collapsedDetails[item.uuid]}
onToggleDetails={onToggleDetails}
collapsedTopic={collapsedTopic[item.uuid]}
onToggleTopic={onToggleTopic}
placement='topicplan'
editPlanMode={editMode}
onDragTopicInPlan={onDragTopic}
onDeleteTopicFromPlan={onDeleteTopic}
index={idx}
key={item.uuid}
/>
);
} else {
return '';
}
default:
return (
<div>Error: unexpected item type: {item.type}</div>
);
}
}
)
...
...
const TopicPlan = ({
...
...
onToggleSection={onToggleSection}
onChangeSectionName={onChangeSectionName}
onSaveSectionName={onSaveSectionName}
onEditSectionName={onEditSectionName}
onDeleteSection={onDeleteSection}
onToggleAllTopics={onToggleAllTopics}
onAddNewTopic={onAddNewTopic}
onDragSection={onDragSection}
collapsedSection={collapsedSection}
collapsedAllTopics={collapsedAllTopics}
editMode={editMode}
tempSectionName={tempSectionName}
editingSectionName={editingSectionName}
collapsedDetails={collapsedDetails}
onToggleDetails={onToggleDetails}
collapsedTopic={collapsedTopic}
onToggleTopic={onToggleTopic}
onDragTopic={onDragTopic}
onDeleteTopic={onDeleteTopic}
onSortEnd={({oldIndex, newIndex}) => console.log('onSortEnd called', oldIndex, newIndex)}
/>
Update: I discovered the answer to my question. It was necessary to have a DOM element between the <SortableContainer> HOC that gets injected and all of the <SortableItem> HOCs that are within it. I wrapped a <div> tag around the code {items.map((item, idx) => { to resolve the issue.
Great component - thank you @clauderic
Damn it! This should go into the docs!
@sconzof You saved my day! 馃憤
I had similar issue. When I've reordered my two images, they reordered, but then stuck and couldn't do anything with it. I've added third image, and then got error from this topic. Nothing worked until I've found out, that the example in docs was causing issues.
The code was:
{items.map((value, index) => (
<SortableItem
key={item-${index}}
index={index}
sortIndex={index}
value={value}
/>
))}
But if you use index as a key, then when reordered, then these keys are mixed up. So I've changed it to use my own id as a key:
{items.map((item, index) => (
<div id={"item_" + item.id} key={item-${item.id}}>
Now everything works just fine.
Thanks, sconzof! I was returning a React.fragment, changed it to <div> and viola!
@degregar you did my day. Many, many thanks!
@degregar and @sconzof thank you very much!
Got me too :( and wasted about 2 hours of my life :(
@sconzof you just saved me a ton of headache. Thank you!!!!!
I am still facing the issue. I'm trying to update database through API every-time after the reordering the grid. It works well for the first time but after first order it shows error.
Error I am getting :-
Cannot read property 'left' of undefined
at WithSortableContainer.animateNodes (react-sortable-hoc.umd.js:1099)
at react-sortable-hoc.umd.js:775
Code: -
`const SortableItem = SortableElement( ({elemIndex, value = '', onRemove}) =>
<GridListTile
style={{ height: '150px', width: '250px', display: 'inline-block', marginRight: '13px'}}
key={value}>
<Button
type='submit'
style={{ position: 'absolute', zIndex: '+1', color: '#EF2D56', marginLeft: '200px'}}
onClick={(e) => {onRemove(elemIndex); console.log('Removed', elemIndex, value)}}>
<Close />
</Button>
<img src={value} alt={value}/>
</GridListTile>
);
const SortableList = SortableContainer(({ items, onRemove}) => {
return (
<ul>
{items.map((value, i) => (
<div id={'item_'+i} key={`item-${value}-${i}`}>
<SortableItem
key={`item-${value}-${i}`}
index={i}
elemIndex={i}
value={value.imageUrl}
onRemove={onRemove}/>
</div>
))}
</ul>
);
});
// inside the render method
<SortableList
axis='xy'
items={this.props.images}
onSortEnd={this.onSortEnd}
onRemove={ i => this.onRemove(i)} />
`
This is also happening when the top level dom element has display: contents - I guess there is no way around this...
Most helpful comment
Update: I discovered the answer to my question. It was necessary to have a DOM element between the
<SortableContainer>HOC that gets injected and all of the<SortableItem>HOCs that are within it. I wrapped a<div>tag around the code{items.map((item, idx) => {to resolve the issue.Great component - thank you @clauderic