michalstruck
michalstruck

Reputation: 88

Some component props reset after drop in react-dnd

I'm building a meme generator app. Here is my main component

  const [inputText, setInputText] = useState<InputText[]>([
    { text: "", top: 30, left: 30 },
    { text: "", top: 45, left: 45 },
  ]);

  //
  //Drag'n'Drop functionality
  //

  const moveText = (
    id: string,
    left: number,
    top: number,
    children: string
  ) => {
    setInputText(
      inputText.map((textObj, i) =>
        i === +id ? { text: children, top, left } : textObj
      )
    );
  };

  const [, drop] = useDrop(() => ({
    accept: ItemTypes.MEME_TEXT,
    drop: (item: DragItem, monitor) => {
      const movementOf = monitor.getDifferenceFromInitialOffset() as XYCoord;
      const left = Math.round(item.left + movementOf.x);
      const top = Math.round(item.top + movementOf.y);
      const { children } = item;
      moveText(item.id, left, top, children);
      return undefined;
    },
    collect: (monitor) => ({ isOver: !!monitor.isOver() }),
  }));

return (<div
        id="generatedMeme"
        className="relative flex items-center justify-center"
      >
        {inputText.map((textObj, i) => (
          <Text key={i} id={i} left={textObj.left} top={textObj.top}>
            {textObj.text}
          </Text>
        ))}
        <img
          ref={drop}
          alt="meme-img"
          src={currentMeme}
          className="rounded-sm max-w-2xl min-w-"
        />
      </div>)

When the <Text /> component gets dragged, and then dropped, every instance of this component disappears, apart from the one dragged. The inputFields array is reduced to a length of 2 (default state). Rerendering (by typing text in an input) the remaining hidden component reveals that it returned to its default position. Console logging revealed that

Here is the <Text /> component.

const Text = ({ children, id, left, top }: props) => {
  const [, drag] = useDrag(
    {
      type: ItemTypes.MEME_TEXT,
      item: { id, left, top, children },
    },
    [children, left, top]
  );

  return (
    <div
      ref={drag}
      className="absolute text-white text-outline text-3xl font-meme uppercase"
      style={{ top, left }}
    >
      {children}
    </div>
  );
};

I thought that maybe it's a weird case of the component not getting the props after moveText is fired, that's why I tried force passing it the children prop, but that doesn't change anything. I'm at a loss as to what to even try.

Upvotes: 2

Views: 383

Answers (1)

michalstruck
michalstruck

Reputation: 88

I didn't add a proper dependency array.

const [, drop] = useDrop(() => ({
    accept: ItemTypes.MEME_TEXT,
    drop: (item: DragItem, monitor) => {
      const movementOf = monitor.getDifferenceFromInitialOffset() as XYCoord;
      const left = Math.round(item.left + movementOf.x);
      const top = Math.round(item.top + movementOf.y);
      const { children } = item;
      moveText(item.id, left, top, children);
      return undefined;
    },
    collect: (monitor) => ({ isOver: !!monitor.isOver() }),
  }), [inputText]);

and the same for useDrag, fixed everything. Turned out react-dnd takes memoization very seriously!

Upvotes: 2

Related Questions