Jorge A. Mendoza II
Jorge A. Mendoza II

Reputation: 91

React DnD useDrop is not using the current state when the method is called

I'm trying to create a todo app with the functionality to drag and drop items around. I'm using React DND and had everything working so far, but when a new item is added to the list, I realized that the function called within the "drop" method in useDrop would only use the state from when the page is first loaded up. No other component has this issue, and when I tried a simple onClick to check the state on the same component, the correct state would be logged as well. The other components (such as a component that inserts a new task) are not having issues at all, this is only happening on this component that is using React DND. I've also checked the react dev tools in chrome and can confirm that the state is being updated as well.
The function that is being called within "drop" is passed down from two components up, and the state that I am trying to use is in that component as well. Any help will be appreciated. Thank You.

export const ToDo = ({
  status,
  todoText,
  todoID,
  deleteTodo,
  updateTodoStatus,
  resortList,
}: TodoProps) => {
  const [, drag] = useDrag(() => ({
    type: 'TODO_DRAG',
    item: { todoID, todoText },
  }));

  const [, drop] = useDrop(() => ({
    accept: 'TODO_DRAG',
    drop: (droppedTodo: dropAndDropItem) => {
      console.log('dropping');
      if (todoID === droppedTodo.todoID) return;
      resortList(todoID, droppedTodo.todoID);
    },
  }));

  return (
    <li ref={drop} onClick={() => resortList(1, 2)}>
      <div ref={drag}>
        <label htmlFor="Change Todo Status">
          <input
            type="checkbox"
            checked={status}
            onChange={() => updateTodoStatus(todoID)}
          />
        </label>
        <p>{todoText}</p>
        <button onClick={() => deleteTodo(todoID)}>X</button>
      </div>
    </li>
  );
};

Upvotes: 9

Views: 4900

Answers (4)

VDN
VDN

Reputation: 737

For those who still have problems with this (like me): according to the documentation, useDrop accepts a specification object, OR a function that creates this specification object. If you don't want/need to pass states from parent to its draggable children, pass to useDrop() an object instead of a function. This should solve your problem (at least it helped for me):

 const [, drop] = useDrop({  // focus here
    accept: 'TODO_DRAG',
    drop: (droppedTodo: dropAndDropItem) => {
      console.log('dropping');
      if (todoID === droppedTodo.todoID) return;
      resortList(todoID, droppedTodo.todoID);
    },
  }); 

Not sure if this solution has some pitfalls, but it works for me for now.

Upvotes: 0

majksec
majksec

Reputation: 76

I've been trying to figure why this happens but it seems like useDrop needs dependency (in this case your state) to make it work inside useDrop.

As i understood it must be in brackets otherwise its not working.

const [, drop] = useDrop(() => ({
    accept: 'TODO_DRAG',
    drop: (droppedTodo: dropAndDropItem) => {
      console.log('dropping');
      if (todoID === droppedTodo.todoID) return;
      resortList(todoID, droppedTodo.todoID);
    },
  }), [StateYouNeedHere]); // focus here

Upvotes: 4

Tristan Marchand
Tristan Marchand

Reputation: 31

I've spent a few days trying to solve this problem with a similar structure to yours (parent states and functions being passed down to a child with dnd).

Through testing of my own, it seems that any state object I change due to a DnD action needs to be listed in the deps array of usedrop (usedrag I am unsure, but I would suspect so).

In my case, I did something like this in the parent component:

let dndDependencies = [state1, state2, ...]
...
<ChildComponentWithDnD dndDependencies = {dndDependencies}/>

I passed this to the child component to insert as dependencies to the useDrop action.

So in your case, it would look something like:

const [, drop] = useDrop(() => ({
accept: 'TODO_DRAG',
drop: (droppedTodo: dropAndDropItem) => {
  console.log('dropping');
  if (todoID === droppedTodo.todoID) return;
  resortList(todoID, droppedTodo.todoID);
},
}), props.dndDependencies);

I am not completely sure if the same applies to useDrag, but adding this just to my useDrops throughout my application seems to have fixed the problem.

I don't particularly like all my child components needing to be passed all the parent states changed by the dnd action, but it does fix the issue.

Upvotes: 3

garma
garma

Reputation: 101

Hi I've been throught the same issue for the past two days. Try this to solve your issue.

const [, drop] = useDrop(() => ({
accept: 'TODO_DRAG',
drop: (droppedTodo: dropAndDropItem) => {
  console.log('dropping');
  if (todoID === droppedTodo.todoID) return;
  resortList(todoID, droppedTodo.todoID);
},}),[todoID]);

Without [todoID] drop won't "see" the update state, but i don't really know why for now.

Upvotes: 9

Related Questions