Octavian Niculescu
Octavian Niculescu

Reputation: 1327

Update array that is transmitted through props

The title might be a little bit ambigous, but I'll explain better :)

So I have this component DraggableGameSlots.tsx

import React, { useState } from "react";
import DraggableGameSlot from "./DraggableGameSlot";

type DraggableGameSlotsProps = {
    numberOfAnswers: number,
    slotFor: string
}

function DraggableGameSlots(props: DraggableGameSlotsProps) {
    const [answers, setAnswers] = useState<string[]>(Array<string>(props.numberOfAnswers).fill("Drop here"));

    return (
        <div className="draggable-game-slots">
            {
                answers.map((val, index) => (
                    <DraggableGameSlot
                        className={props.slotFor === "text" ? "draggable-game-slot-for-text" : "draggable-game-slot-for-image"}
                        key={index}
                        answers={answers}
                        setAnswers={setAnswers}
                    />
                ))
            }
        </div>
    );
}

export default DraggableGameSlots;

and DraggableGameSlot.tsx

import { useEffect, useRef } from 'react';
import { useDrop } from 'react-dnd';
import './css/DraggableGameSlot.css';

type DraggableGameSlotProps = {
    className: string,
    answers: any,
    setAnswers: any
}

function DraggableGameSlot(props: DraggableGameSlotProps) {          
    const [{isOver}, drop] = useDrop(() => ({
        accept: "image",
        drop(item: {id: string}) {
            props.setAnswers([...props.answers, item.id]);
            console.log(props.answers);
        },
        collect: (monitor) => ({
            isOver: !!monitor.isOver(),
        })
    }))

    useEffect(() =>
        console.log(props.answers)
    );
      
    return (
        <div className={`draggable-game-slot ${props.className}`} ref={drop}>
            <span>Drop here</span>
        </div>
    )
}

export default DraggableGameSlot;

The logic is as follows:

I have an array of answers and I want to add new answers using react-dnd. The initial answers are "Drop here" (because they are not defined, only the slots with this text will appear - I'll implement this logic later).

So I have an array which contains all of the answers. I render them and everything is fine.

Then I have an useDrop hook from react-dnd - it is used to define the drop target for the draggable items. That is not the point of the posts either :)

I observed using the two console.logs from DraggableGameSlot, that for numberOfAnswers = 4, my array will be either length = 4 (initially) or length = 5, when I drop an answer and drop is called.

I expected each time an answer is dropped and props.setAnswers is called, a new answer will be added at the end of the array. Well, it doesn't work like this, actually, the 5th item is changed everytime.

e.g.

Drop here Drop here Drop here Drop here

I drag answer1:

Expectation: Drop here Drop here Drop here Drop here answer1
Behavior: Drop here Drop here Drop here Drop here answer1
OK

I drag answer2:
Expectation: Drop here Drop here Drop here Drop here answer1 answer2
Behavior: Drop here Drop here Drop here Drop here answer2

NOT OK

I suspect that it happens because props.answers doesn't update. I probably get a copy of its value when passing it through props, not a reference, so when I call setAnswers the answers array is updated only in DraggableGameSlots and props.answers stay the same.

Well, I don't want that.

Why is this happening and how can I make it work the way I intend? :)

Upvotes: 1

Views: 224

Answers (1)

bigless
bigless

Reputation: 3111

When looking closer to useDrop, it uses memoization(dependency array) and empty array as default value. You should add array of dependencies that can change(answers, setAnswers, etc) as second parameter to useDrop.

Upvotes: 1

Related Questions